Files
jaildoctors_dilemma/source/external/fkyaml_node.hpp

14727 lines
586 KiB
C++

// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_NODE_HPP
#define FK_YAML_NODE_HPP
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <initializer_list>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
// #include <fkYAML/detail/macros/define_macros.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP
#define FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP
// #include <fkYAML/detail/macros/version_macros.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
// Check version definitions if already defined.
#if defined(FK_YAML_MAJOR_VERSION) && defined(FK_YAML_MINOR_VERSION) && defined(FK_YAML_PATCH_VERSION)
#if FK_YAML_MAJOR_VERSION != 0 || FK_YAML_MINOR_VERSION != 4 || FK_YAML_PATCH_VERSION != 2
#warning Already included a different version of the fkYAML library!
#else
// define macros to skip defining macros down below.
#define FK_YAML_VERCHECK_SUCCEEDED
#endif
#endif
#ifndef FK_YAML_VERCHECK_SUCCEEDED
#define FK_YAML_MAJOR_VERSION 0
#define FK_YAML_MINOR_VERSION 4
#define FK_YAML_PATCH_VERSION 2
#define FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch) v##major##_##minor##_##patch
#define FK_YAML_NAMESPACE_VERSION_CONCAT(major, minor, patch) FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch)
#define FK_YAML_NAMESPACE_VERSION \
FK_YAML_NAMESPACE_VERSION_CONCAT(FK_YAML_MAJOR_VERSION, FK_YAML_MINOR_VERSION, FK_YAML_PATCH_VERSION)
#define FK_YAML_NAMESPACE_BEGIN \
namespace fkyaml { \
inline namespace FK_YAML_NAMESPACE_VERSION {
#define FK_YAML_NAMESPACE_END \
} /* inline namespace FK_YAML_NAMESPACE_VERSION */ \
} // namespace fkyaml
#define FK_YAML_DETAIL_NAMESPACE_BEGIN \
FK_YAML_NAMESPACE_BEGIN \
namespace detail {
#define FK_YAML_DETAIL_NAMESPACE_END \
} /* namespace detail */ \
FK_YAML_NAMESPACE_END
#endif // !defined(FK_YAML_VERCHECK_SUCCEEDED)
// #include <fkYAML/detail/macros/cpp_config_macros.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP
#define FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP
// This file is assumed to be included only by version_macros.hpp file.
// To avoid redundant inclusion, do not include version_macros.hpp file as the other files do.
// With the MSVC compilers, the value of __cplusplus is by default always "199611L"(C++98).
// To avoid that, the library instead references _MSVC_LANG which is always set a correct value.
// See https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ for more details.
#if defined(_MSVC_LANG) && !defined(__clang__)
#define FK_YAML_CPLUSPLUS _MSVC_LANG
#else
#define FK_YAML_CPLUSPLUS __cplusplus
#endif
// C++ language standard detection
// Skip detection if the definitions listed below already exist.
#if !defined(FK_YAML_HAS_CXX_23) && !defined(FK_YAML_HAS_CXX_20) && !defined(FK_YAML_HAS_CXX_17) && \
!defined(FK_YAML_HAS_CXX_14) && !defined(FK_YAML_CXX_11)
#if FK_YAML_CPLUSPLUS >= 202302L
#define FK_YAML_HAS_CXX_23
#define FK_YAML_HAS_CXX_20
#define FK_YAML_HAS_CXX_17
#define FK_YAML_HAS_CXX_14
#elif FK_YAML_CPLUSPLUS >= 202002L
#define FK_YAML_HAS_CXX_20
#define FK_YAML_HAS_CXX_17
#define FK_YAML_HAS_CXX_14
#elif FK_YAML_CPLUSPLUS >= 201703L
#define FK_YAML_HAS_CXX_17
#define FK_YAML_HAS_CXX_14
#elif FK_YAML_CPLUSPLUS >= 201402L
#define FK_YAML_HAS_CXX_14
#endif
// C++11 is the minimum required version of the fkYAML library.
#define FK_YAML_HAS_CXX_11
#endif
// switch usage of the deprecated attribute. [[deprecated]] is available since C++14.
#if defined(FK_YAML_HAS_CXX_14)
#define FK_YAML_DEPRECATED(msg) [[deprecated(msg)]]
#else
#if defined(_MSC_VER)
#define FK_YAML_DEPRECATED(msg) __declspec(deprecated(msg))
#elif defined(__GNUC__) || defined(__clang__)
#define FK_YAML_DEPRECATED(msg) __attribute__((deprecated(msg)))
#else
#define FK_YAML_DEPRECATED(msg)
#endif
#endif
// switch usage of inline variables which have been available since C++17.
#if defined(FK_YAML_HAS_CXX_17)
#define FK_YAML_INLINE_VAR inline
#else
#define FK_YAML_INLINE_VAR
#endif
// switch usage of constexpr keyword depending on active C++ standard.
#if defined(FK_YAML_HAS_CXX_17)
#define FK_YAML_CXX17_CONSTEXPR constexpr
#else
#define FK_YAML_CXX17_CONSTEXPR
#endif
// Detect __has_* macros.
// The following macros replace redundant `defined(__has_*) && __has_*(...)`.
#ifdef __has_include
#define FK_YAML_HAS_INCLUDE(header) __has_include(header)
#else
#define FK_YAML_HAS_INCLUDE(header) (0)
#endif
#ifdef __has_builtin
#define FK_YAML_HAS_BUILTIN(builtin) __has_builtin(builtin)
#else
#define FK_YAML_HAS_BUILTIN(builtin) (0)
#endif
#ifdef __has_cpp_attribute
#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr)
#else
#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) (0)
#endif
#ifdef __has_feature
#define FK_YAML_HAS_FEATURE(feat) __has_feature(feat)
#else
#define FK_YAML_HAS_FEATURE(feat) (0)
#endif
// switch usage of the no_sanitize attribute only when Clang sanitizer is active.
#if defined(__clang__) && FK_YAML_HAS_FEATURE(address_sanitizer)
#define FK_YAML_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))
#else
#define FK_YAML_NO_SANITIZE(...)
#endif
#if FK_YAML_HAS_INCLUDE(<version>)
// <version> is available since C++20
#include <version>
#endif
//
// C++ feature detections
//
// switch usages of the std::to_chars()/std::from_chars() functions which have been available since C++17.
#if defined(FK_YAML_HAS_CXX_17) && defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
#define FK_YAML_HAS_TO_CHARS (1)
#else
#define FK_YAML_HAS_TO_CHARS (0)
#endif
// switch usage of char8_t which has been available since C++20.
#if defined(FK_YAML_HAS_CXX_20) && defined(__cpp_char8_t) && __cpp_char8_t >= 201811L
#define FK_YAML_HAS_CHAR8_T (1)
#else
#define FK_YAML_HAS_CHAR8_T (0)
#endif
//
// utility macros
//
// switch usage of [[likely]] C++ attribute which has been available since C++20.
#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(likely) >= 201803L
#define FK_YAML_LIKELY(expr) (!!(expr)) [[likely]]
#elif FK_YAML_HAS_BUILTIN(__builtin_expect)
#define FK_YAML_LIKELY(expr) (__builtin_expect(!!(expr), 1))
#else
#define FK_YAML_LIKELY(expr) (!!(expr))
#endif
// switch usage of [[unlikely]] C++ attribute which has been available since C++20.
#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(unlikely) >= 201803L
#define FK_YAML_UNLIKELY(expr) (!!(expr)) [[unlikely]]
#elif FK_YAML_HAS_BUILTIN(__builtin_expect)
#define FK_YAML_UNLIKELY(expr) (__builtin_expect(!!(expr), 0))
#else
#define FK_YAML_UNLIKELY(expr) (!!(expr))
#endif
#endif /* FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP */
#endif /* FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP */
// #include <fkYAML/detail/assert.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ASSERT_HPP
#define FK_YAML_DETAIL_ASSERT_HPP
// if FK_YAML_ASSERT is not user-defined. apply the default assert impl.
#ifndef FK_YAML_ASSERT
#ifndef NDEBUG
#include <cassert>
#define FK_YAML_ASSERT(x) assert(x)
#else
#define FK_YAML_ASSERT(x)
#endif
#endif
#endif /* FK_YAML_DETAIL_ASSERT_HPP */
// #include <fkYAML/detail/document_metainfo.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP
#define FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP
#include <string>
#include <map>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_META_NODE_TRAITS_HPP
#define FK_YAML_DETAIL_META_NODE_TRAITS_HPP
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/detect.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_META_DETECT_HPP
#define FK_YAML_DETAIL_META_DETECT_HPP
#include <iterator>
#include <type_traits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP
#define FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP
#include <cstddef>
#include <type_traits>
// #include <fkYAML/detail/macros/define_macros.hpp>
#ifdef FK_YAML_HAS_CXX_14
#include <utility>
#endif
FK_YAML_DETAIL_NAMESPACE_BEGIN
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// For contributors:
// This file is for supplementing future C++ STL implementations to utilize some useful features
// implemented in C++14 or better.
// This file is needed to keep the fkYAML library requirement to C++11.
// **DO NOT** implement features which are not included any version of STL in this file.
// Such implementations must be in the type_traits.hpp file.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef FK_YAML_HAS_CXX_14
/// @brief An alias template for std::add_pointer::type with C++11.
/// @note std::add_pointer_t is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/add_pointer
/// @tparam T A type to be added a pointer.
template <typename T>
using add_pointer_t = typename std::add_pointer<T>::type;
/// @brief An alias template for std::enable_if::type with C++11.
/// @note std::enable_if_t is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/enable_if
/// @tparam Condition A condition tested at compile time.
/// @tparam T The type defined only if Condition is true.
template <bool Condition, typename T = void>
using enable_if_t = typename std::enable_if<Condition, T>::type;
/// @brief A simple implementation to use std::is_null_pointer with C++11.
/// @note std::is_null_pointer is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/is_null_pointer
/// @tparam T The type to be checked if it's equal to std::nullptr_t.
template <typename T>
struct is_null_pointer : std::is_same<std::nullptr_t, typename std::remove_cv<T>::type> {};
/// @brief An alias template for std::remove_cv::type with C++11.
/// @note std::remove_cv_t is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/remove_cv
/// @tparam T A type from which const-volatile qualifiers are removed.
template <typename T>
using remove_cv_t = typename std::remove_cv<T>::type;
/// @brief An alias template for std::remove_pointer::type with C++11.
/// @note std::remove_pointer_t is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/remove_pointer
/// @tparam T A type from which a pointer is removed.
template <typename T>
using remove_pointer_t = typename std::remove_pointer<T>::type;
/// @brief An alias template for std::remove_reference::type with C++11.
/// @note std::remove_reference_t is available since C++14.
/// @sa https://en.cppreference.com/w/cpp/types/remove_reference
/// @tparam T A type from which a reference is removed.
template <typename T>
using remove_reference_t = typename std::remove_reference<T>::type;
template <typename T, T... I>
struct integer_sequence {
using value_type = T;
static constexpr std::size_t size() noexcept {
return sizeof...(I);
}
};
#if !FK_YAML_HAS_BUILTIN(__make_integer_seq) && !FK_YAML_HAS_BUILTIN(__integer_pack)
namespace make_int_seq_impl {
template <typename IntSeq0, typename IntSeq1>
struct merger;
template <typename T, T... Ints0, T... Ints1>
struct merger<integer_sequence<T, Ints0...>, integer_sequence<T, Ints1...>> {
using type = integer_sequence<T, Ints0..., (sizeof...(Ints0) + Ints1)...>;
};
template <typename T, std::size_t Num>
struct generator {
using type =
typename merger<typename generator<T, Num / 2>::type, typename generator<T, Num - Num / 2>::type>::type;
};
template <typename T>
struct generator<T, 0> {
using type = integer_sequence<T>;
};
template <typename T>
struct generator<T, 1> {
using type = integer_sequence<T, 0>;
};
} // namespace make_int_seq_impl
#endif
template <typename T, T Num>
using make_integer_sequence
#if FK_YAML_HAS_BUILTIN(__make_integer_seq)
// clang defines built-in __make_integer_seq to generate an integer sequence.
= __make_integer_seq<integer_sequence, T, Num>;
#elif FK_YAML_HAS_BUILTIN(__integer_pack)
// GCC or other compilers may implement built-in __integer_pack to generate an
// integer sequence.
= integer_sequence<T, __integer_pack(Num)...>;
#else
// fallback to the library implementation of make_integer_sequence.
= typename make_int_seq_impl::generator<T, Num>::type;
#endif
template <std::size_t... Idx>
using index_sequence = integer_sequence<std::size_t, Idx...>;
template <std::size_t Num>
using make_index_sequence = make_integer_sequence<std::size_t, Num>;
template <typename... Types>
using index_sequence_for = make_index_sequence<sizeof...(Types)>;
#else // !defined(FK_YAML_HAS_CXX_14)
using std::add_pointer_t;
using std::enable_if_t;
using std::index_sequence;
using std::index_sequence_for;
using std::integer_sequence;
using std::is_null_pointer;
using std::make_index_sequence;
using std::make_integer_sequence;
using std::remove_cv_t;
using std::remove_pointer_t;
using std::remove_reference_t;
#endif // !defined(FK_YAML_HAS_CXX_14)
#ifndef FK_YAML_HAS_CXX_17
/// @brief A simple implementation to use std::bool_constant with C++11/C++14.
/// @tparam Val
template <bool Val>
using bool_constant = std::integral_constant<bool, Val>;
/// @brief A simple implementation to use std::void_t with C++11/C++14.
/// @note
/// std::conjunction is available since C++17.
/// This is applied when no traits are specified as inputs.
/// @sa https://en.cppreference.com/w/cpp/types/conjunction
/// @tparam Traits Type traits to be checked if their ::value are all true.
template <typename... Traits>
struct conjunction : std::true_type {};
/// @brief A partial specialization of conjunction if only one Trait is given.
/// @tparam Trait Type trait to be checked if its ::value is true.
template <typename Trait>
struct conjunction<Trait> : Trait {};
/// @brief A partial specialization of conjunction if more than one traits are given.
/// @tparam First The first type trait to be checked if its ::value is true.
/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is true.
template <typename First, typename... Rest>
struct conjunction<First, Rest...> : std::conditional<First::value, conjunction<Rest...>, First>::type {};
/// @brief A simple implementation to use std::disjunction with C++11/C++14.
/// @note
/// std::disjunction is available since C++17.
/// This is applied when no traits are specified as inputs.
/// @sa https://en.cppreference.com/w/cpp/types/disjunction
/// @tparam Traits Type traits to be checked if at least one of their ::value is true.
template <typename... Traits>
struct disjunction : std::false_type {};
/// @brief A partial specialization of disjunction if only one Trait is given.
/// @tparam Trait Type trait to be checked if its ::value is true.
template <typename Trait>
struct disjunction<Trait> : Trait {};
/// @brief A partial specialization of disjunction if more than one traits are given.
/// @tparam First The first type trait to be checked if its ::value is true.
/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is false.
template <typename First, typename... Rest>
struct disjunction<First, Rest...> : std::conditional<First::value, First, disjunction<Rest...>>::type {};
/// @brief A simple implementation to use std::negation with C++11/C++14.
/// @note std::negation is available since C++17.
/// @sa https://en.cppreference.com/w/cpp/types/negation
/// @tparam Trait Type trait whose ::value is negated.
template <typename Trait>
struct negation : std::integral_constant<bool, !Trait::value> {};
/// @brief A helper for void_t.
/// @tparam Types Any types to be transformed to void type.
template <typename... Types>
struct make_void {
using type = void;
};
/// @brief A simple implementation to use std::void_t with C++11/C++14.
/// @note std::void_t is available since C++17.
/// @sa https://en.cppreference.com/w/cpp/types/void_t
/// @tparam Types Any types to be transformed to void type.
template <typename... Types>
using void_t = typename make_void<Types...>::type;
#else // !defined(FK_YAML_HAS_CXX_17)
using std::bool_constant;
using std::conjunction;
using std::disjunction;
using std::negation;
using std::void_t;
#endif // !defined(FK_YAML_HAS_CXX_17)
#ifndef FK_YAML_HAS_CXX_20
/// @brief A simple implementation to use std::remove_cvref_t with C++11/C++14/C++17.
/// @note std::remove_cvref & std::remove_cvref_t are available since C++20.
/// @sa https://en.cppreference.com/w/cpp/types/remove_cvref
/// @tparam T A type from which cv-qualifiers and reference are removed.
template <typename T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#else
using std::remove_cvref_t;
#endif
/// @brief A wrapper function to call std::unreachable() (since C++23) or similar compiler specific extensions.
/// @note This function is implemented only for better code optimization against dead code and thus excluded from
/// coverage report.
// LCOV_EXCL_START
[[noreturn]] inline void unreachable() {
// use compiler specific extensions if possible.
// undefined behavior should be raised by an empty function with noreturn attribute.
#if defined(FK_YAML_HAS_CXX_23) || (defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L)
std::unreachable();
#elif defined(_MSC_VER) && !defined(__clang__) // MSVC
__assume(false);
#else
__builtin_unreachable();
#endif
}
// LCOV_EXCL_STOP
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A dummy struct to represent detection failure.
struct nonesuch {
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(const nonesuch&) = delete;
nonesuch(nonesuch&&) = delete;
nonesuch& operator=(const nonesuch&) = delete;
nonesuch& operator=(nonesuch&&) = delete;
};
/// @brief namespace to implement detector type traits
namespace detector_impl {
/// @brief A helper for general type detection.
/// @tparam Default A type to represent detection failure.
/// @tparam AlwaysVoid This must be void type.
/// @tparam Op A type for desired operation type.
/// @tparam Args Argument types passed to desired operation.
template <typename Default, typename AlwaysVoid, template <typename...> class Op, typename... Args>
struct detector : std::false_type {
/// @brief A type which represents detection failure.
using type = Default;
};
/// @brief A partial specialization of detector if desired operation type is found.
/// @tparam Default A type to represent detection failure.
/// @tparam Op A type for desired operation type.
/// @tparam Args Argument types passed to desired operation.
template <typename Default, template <typename...> class Op, typename... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...> : std::true_type {
/// @brief A detected type.
using type = Op<Args...>;
};
} // namespace detector_impl
/// @brief Type traits to detect Op operation with Args argument types
/// @tparam Op A desired operation type.
/// @tparam Args Argument types passed to desired operation.
template <template <typename...> class Op, typename... Args>
using is_detected = detector_impl::detector<nonesuch, void, Op, Args...>;
/// @brief Type traits to represent a detected type.
/// @tparam Op A type for desired operation type.
/// @tparam Args Argument types passed to desired operation.
template <template <typename...> class Op, typename... Args>
using detected_t = typename detector_impl::detector<nonesuch, void, Op, Args...>::type;
/// @brief Type traits to check if Expected and a detected type are exactly the same.
/// @tparam Expected An expected detection result type.
/// @tparam Op A type for desired operation.
/// @tparam Args Argument types passed to desired operation.
template <typename Expected, template <typename...> class Op, typename... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
/// @brief namespace for member type detections of aliases and functions.
namespace detect {
/// @brief The type which represents `iterator` member type.
/// @tparam T A target type.
template <typename T>
using iterator_t = typename T::iterator;
/// @brief The type which represents `key_type` member type.
/// @tparam T A target type.
template <typename T>
using key_type_t = typename T::key_type;
/// @brief The type which represents `mapped_type` member type.
/// @tparam T A target type.
template <typename T>
using mapped_type_t = typename T::mapped_type;
/// @brief The type which represents `value_type` member type.
/// @tparam T A target type.
template <typename T>
using value_type_t = typename T::value_type;
/// @brief The type which represents `difference_type` member type.
/// @tparam T A target type.
template <typename T>
using difference_type_t = typename T::difference_type;
/// @brief The type which represents `pointer` member type.
/// @tparam T A target type.
template <typename T>
using pointer_t = typename T::pointer;
/// @brief The type which represents `reference` member type.
/// @tparam T A target type.
template <typename T>
using reference_t = typename T::reference;
/// @brief The type which represents `iterator_category` member type.
/// @tparam T A target type.
template <typename T>
using iterator_category_t = typename T::iterator_category;
/// @brief The type which represents `container_type` member type.
/// @tparam T A target type.
template <typename T>
using container_type_t = typename T::container_type;
/// @brief The type which represents emplace member function.
/// @tparam T A target type.
template <typename T, typename... Args>
using emplace_fn_t = decltype(std::declval<T>().emplace(std::declval<Args>()...));
/// @brief The type which represents reserve member function.
/// @tparam T A target type.
template <typename T>
using reserve_fn_t = decltype(std::declval<T>().reserve(std::declval<typename remove_cvref_t<T>::size_type>()));
/// @brief Type traits to check if T has `iterator` member type.
/// @tparam T A target type.
template <typename T>
using has_iterator = is_detected<iterator_t, remove_cvref_t<T>>;
/// @brief Type traits to check if T has `key_type` member type.
/// @tparam T A target type.
template <typename T>
using has_key_type = is_detected<key_type_t, remove_cvref_t<T>>;
/// @brief Type traits to check if T has `mapped_type` member type.
/// @tparam T A target type.
template <typename T>
using has_mapped_type = is_detected<mapped_type_t, remove_cvref_t<T>>;
/// @brief Type traits to check if T has `value_type` member type.
/// @tparam T A target type.
template <typename T>
using has_value_type = is_detected<value_type_t, remove_cvref_t<T>>;
/// @brief Type traits to check if T is a std::iterator_traits like type.
/// @tparam T A target type.
template <typename T>
struct is_iterator_traits : conjunction<
is_detected<difference_type_t, remove_cvref_t<T>>, has_value_type<remove_cvref_t<T>>,
is_detected<pointer_t, remove_cvref_t<T>>, is_detected<reference_t, remove_cvref_t<T>>,
is_detected<iterator_category_t, remove_cvref_t<T>>> {};
/// @brief Type traits to check if T has `container_type` member type.
/// @tparam T A target type.
template <typename T>
using has_container_type = is_detected<container_type_t, remove_cvref_t<T>>;
/// @brief Type traits to check if T has reserve member function.
/// @tparam T A target type.
template <typename T>
using has_reserve = is_detected<reserve_fn_t, T>;
// fallback to these STL functions.
using std::begin;
using std::end;
/// @brief Type traits to check if begin/end functions can be called on a T object.
/// @tparam T A target type.
template <typename T, typename = void>
struct has_begin_end : std::false_type {};
/// @brief Type traits to check if begin/end functions can be called on a T object.
/// @tparam T A target type.
template <typename T>
struct has_begin_end<T, void_t<decltype(begin(std::declval<T>()), end(std::declval<T>()))>> : std::true_type {};
} // namespace detect
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_META_DETECT_HPP */
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_META_TYPE_TRAITS_HPP
#define FK_YAML_DETAIL_META_TYPE_TRAITS_HPP
#include <iterator>
#include <type_traits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Type trait to check if T and U are comparable types.
/// @tparam Comparator An object type to compare T and U objects.
/// @tparam T A type for comparison.
/// @tparam U The other type for comparison.
/// @tparam typename Placeholder for determining T and U are comparable types.
template <typename Comparator, typename T, typename U, typename = void>
struct is_comparable : std::false_type {};
/// @brief A partial specialization of is_comparable if T and U are comparable types.
/// @tparam Comparator An object type to compare T and U objects.
/// @tparam T A type for comparison.
/// @tparam U The other type for comparison.
template <typename Comparator, typename T, typename U>
struct is_comparable<
Comparator, T, U,
void_t<
decltype(std::declval<Comparator>()(std::declval<T>(), std::declval<U>())),
decltype(std::declval<Comparator>()(std::declval<U>(), std::declval<T>()))>> : std::true_type {};
/// @brief Type trait to check if KeyType can be used as key type.
/// @tparam Comparator An object type to compare T and U objects.
/// @tparam ObjectKeyType The original key type.
/// @tparam KeyType A type to be used as key type.
template <typename Comparator, typename ObjectKeyType, typename KeyType>
using is_usable_as_key_type = is_comparable<Comparator, ObjectKeyType, KeyType>;
/// @brief Type trait to check if T is of non-boolean integral types.
/// @tparam T A type to be checked.
template <typename T>
using is_non_bool_integral = conjunction<std::is_integral<T>, negation<std::is_same<bool, T>>>;
/// @brief Type traits to check if T is a complete type.
/// @tparam T A type to be checked if a complete type.
/// @tparam typename N/A
template <typename T, typename = void>
struct is_complete_type : std::false_type {};
/// @brief A partial specialization of is_complete_type if T is a complete type.
/// @tparam T
template <typename T>
struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
/// @brief A utility alias to test if the value type of `ItrType` is `T`.
/// @tparam ItrType An iterator type.
/// @tparam T The target iterator value type.
template <typename ItrType, typename T>
using is_iterator_of = std::is_same<remove_cv_t<typename std::iterator_traits<ItrType>::value_type>, T>;
/// @brief A utility struct to generate static constant instance.
/// @tparam T A target type for the resulting static constant instance.
template <typename T>
struct static_const {
static FK_YAML_INLINE_VAR constexpr T value {}; // NOLINT(readability-identifier-naming)
};
#ifndef FK_YAML_HAS_CXX_17
/// @brief A instantiation of static_const::value instance.
/// @note This is required if inline variables are not available. C++11-14 do not provide such a feature yet.
/// @tparam T A target type for the resulting static constant instance.
template <typename T>
constexpr T static_const<T>::value;
#endif
/// @brief A helper structure for tag dispatch.
/// @tparam T A tag type.
template <typename T>
struct type_tag {
/// @brief A tagged type.
using type = T;
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_META_TYPE_TRAITS_HPP */
// #include <fkYAML/fkyaml_fwd.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_FKYAML_FWD_HPP
#define FK_YAML_FKYAML_FWD_HPP
#include <cstdint>
#include <map>
#include <string>
#include <vector>
// #include <fkYAML/detail/macros/version_macros.hpp>
FK_YAML_NAMESPACE_BEGIN
/// @brief An ADL friendly converter between basic_node objects and native data objects.
/// @tparam ValueType A target data type.
/// @sa https://fktn-k.github.io/fkYAML/api/node_value_converter/
template <typename ValueType, typename = void>
class node_value_converter;
/// @brief A class to store value of YAML nodes.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/
template <
template <typename, typename...> class SequenceType = std::vector,
template <typename, typename, typename...> class MappingType = std::map, typename BooleanType = bool,
typename IntegerType = std::int64_t, typename FloatNumberType = double, typename StringType = std::string,
template <typename, typename = void> class ConverterType = node_value_converter>
class basic_node;
/// @brief default YAML node value container.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/node/
using node = basic_node<>;
/// @brief A minimal map-like container which preserves insertion order.
/// @tparam Key A type for keys.
/// @tparam Value A type for values.
/// @tparam IgnoredCompare A placeholder for key comparison. This will be ignored.
/// @tparam Allocator A class for allocators.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
template <typename Key, typename Value, typename IgnoredCompare, typename Allocator>
class ordered_map;
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_FKYAML_FWD_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/////////////////////////////
// is_basic_node traits
/////////////////////////////
/// @brief Actual implementation of the is_basic_node type traits struct.
/// @tparam T A class to be checked if it's a basic_node template class instance type.
template <typename T>
struct is_basic_node_impl : std::false_type {};
/// @brief A partial specialization of is_basic_node_impl for basic_node template class.
/// @tparam SequenceType A type for sequence node value containers.
/// @tparam MappingType A type for mapping node value containers.
/// @tparam BooleanType A type for boolean node values.
/// @tparam IntegerType A type for integer node values.
/// @tparam FloatNumberType A type for float number node values.
/// @tparam StringType A type for string node values.
/// @tparam Converter A type for node-value converter
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename> class Converter>
struct is_basic_node_impl<
basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, Converter>>
: std::true_type {};
/// @brief A struct to check the template parameter class is a basic_node template class instance type.
/// @tparam T A class to be checked if it's a basic_node template class instance type.
template <typename T>
struct is_basic_node : is_basic_node_impl<remove_cvref_t<T>> {};
///////////////////////////////////
// is_node_ref_storage traits
///////////////////////////////////
// forward declaration for node_ref_storage<...>
template <typename>
class node_ref_storage;
/// @brief A struct to check the template parameter class is a kind of node_ref_storage_template class.
/// @tparam T A type to be checked if it's a kind of node_ref_storage template class.
template <typename T>
struct is_node_ref_storage : std::false_type {};
/// @brief A partial specialization for node_ref_storage template class.
/// @tparam T A template parameter type of node_ref_storage template class.
template <typename T>
struct is_node_ref_storage<node_ref_storage<T>> : std::true_type {};
///////////////////////////////////////////////////////
// basic_node conversion API representative types
///////////////////////////////////////////////////////
/// @brief A type represent from_node function.
/// @tparam T A type which provides from_node function.
/// @tparam Args Argument types passed to from_node function.
template <typename T, typename... Args>
using from_node_function_t = decltype(T::from_node(std::declval<Args>()...));
/// @brief A type which represent to_node function.
/// @tparam T A type which provides to_node function.
/// @tparam Args Argument types passed to to_node function.
template <typename T, typename... Args>
using to_node_function_t = decltype(T::to_node(std::declval<Args>()...));
///////////////////////////////////////////////////
// basic_node conversion API detection traits
///////////////////////////////////////////////////
/// @brief Type traits to check if T is a compatible type for BasicNodeType in terms of from_node function.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam T A target type passed to from_node function.
/// @tparam typename N/A
template <typename BasicNodeType, typename T, typename = void>
struct has_from_node : std::false_type {};
/// @brief A partial specialization of has_from_node if T is not a basic_node template instance type.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam T A target type passed to from_node function.
template <typename BasicNodeType, typename T>
struct has_from_node<BasicNodeType, T, enable_if_t<negation<is_basic_node<T>>::value>> {
using converter = typename BasicNodeType::template value_converter_type<T, void>;
// NOLINTNEXTLINE(readability-identifier-naming)
static constexpr bool value =
is_detected_exact<void, from_node_function_t, converter, const BasicNodeType&, T&>::value;
};
/// @brief Type traits to check if T is a compatible type for BasicNodeType in terms of to_node function.
/// @warning Do not pass basic_node type as BasicNodeType to avoid infinite type instantiation.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam T A target type passed to to_node function.
/// @tparam typename N/A
template <typename BasicNodeType, typename T, typename = void>
struct has_to_node : std::false_type {};
/// @brief A partial specialization of has_to_node if T is not a basic_node template instance type.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam T A target type passed to to_node function.
template <typename BasicNodeType, typename T>
struct has_to_node<BasicNodeType, T, enable_if_t<negation<is_basic_node<T>>::value>> {
using converter = typename BasicNodeType::template value_converter_type<T, void>;
// NOLINTNEXTLINE(readability-identifier-naming)
static constexpr bool value = is_detected_exact<void, to_node_function_t, converter, BasicNodeType&, T>::value;
};
///////////////////////////////////////
// is_node_compatible_type traits
///////////////////////////////////////
/// @brief Type traits implementation of is_node_compatible_type to check if CompatibleType is a compatible type for
/// BasicNodeType.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam CompatibleType A target type for compatibility check.
/// @tparam typename N/A
template <typename BasicNodeType, typename CompatibleType, typename = void>
struct is_node_compatible_type_impl : std::false_type {};
/// @brief A partial specialization of is_node_compatible_type_impl if CompatibleType is a complete type and is
/// compatible for BasicNodeType.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam CompatibleType A target type for compatibility check.
template <typename BasicNodeType, typename CompatibleType>
struct is_node_compatible_type_impl<
BasicNodeType, CompatibleType,
enable_if_t<conjunction<is_complete_type<CompatibleType>, has_to_node<BasicNodeType, CompatibleType>>::value>>
: std::true_type {};
/// @brief Type traits to check if CompatibleType is a compatible type for BasicNodeType.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam CompatibleType A target type for compatibility check.
template <typename BasicNodeType, typename CompatibleType>
struct is_node_compatible_type : is_node_compatible_type_impl<BasicNodeType, CompatibleType> {};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_META_NODE_TRAITS_HPP */
// #include <fkYAML/yaml_version_type.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_YAML_VERSION_TYPE_HPP
#define FK_YAML_YAML_VERSION_TYPE_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
FK_YAML_NAMESPACE_BEGIN
enum class yaml_version_type : std::uint8_t {
VERSION_1_1, //!< YAML version 1.1
VERSION_1_2, //!< YAML version 1.2
};
inline const char* to_string(yaml_version_type t) noexcept {
switch (t) {
case yaml_version_type::VERSION_1_1:
return "VERSION_1_1";
case yaml_version_type::VERSION_1_2:
return "VERSION_1_2";
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_YAML_VERSION_TYPE_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief The set of directives for a YAML document.
template <typename BasicNodeType, typename = enable_if_t<is_basic_node<BasicNodeType>::value>>
struct document_metainfo {
/// The YAML version used for the YAML document.
yaml_version_type version {yaml_version_type::VERSION_1_2};
/// Whether the YAML version has been specified.
bool is_version_specified {false};
/// The prefix of the primary handle.
std::string primary_handle_prefix;
/// The prefix of the secondary handle.
std::string secondary_handle_prefix;
/// The map of handle-prefix pairs.
std::map<std::string /*handle*/, std::string /*prefix*/> named_handle_map;
/// The map of anchor node which allows for key duplication.
std::multimap<std::string /*anchor name*/, BasicNodeType> anchor_table {};
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP */
// #include <fkYAML/detail/exception_safe_allocation.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_EXCEPTION_SAFE_ALLOCATION_HPP
#define FK_YAML_DETAIL_EXCEPTION_SAFE_ALLOCATION_HPP
#include <memory>
#include <utility>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Helper struct which ensures destruction/deallocation of heap-allocated objects.
/// @tparam ObjT Object type.
/// @tparam AllocTraits Allocator traits type for the object.
template <typename ObjT, typename AllocTraits>
struct tidy_guard {
tidy_guard() = delete;
/// @brief Construct a tidy_guard with a pointer to the object.
/// @param p_obj
tidy_guard(ObjT* p_obj) noexcept
: p_obj(p_obj) {
}
// move-only
tidy_guard(const tidy_guard&) = delete;
tidy_guard& operator=(const tidy_guard&) = delete;
/// @brief Move constructs a tidy_guard object.
tidy_guard(tidy_guard&&) = default;
/// @brief Move assigns a tidy_guard object.
/// @return Reference to this tidy_guard object.
tidy_guard& operator=(tidy_guard&&) = default;
/// @brief Destroys this tidy_guard object. Destruction/deallocation happen if the pointer is not null.
~tidy_guard() {
if FK_YAML_UNLIKELY (p_obj != nullptr) {
typename AllocTraits::allocator_type alloc {};
AllocTraits::destroy(alloc, p_obj);
AllocTraits::deallocate(alloc, p_obj, 1);
p_obj = nullptr;
}
}
/// @brief Get the pointer to the object.
/// @return The pointer to the object.
ObjT* get() const noexcept {
return p_obj;
}
/// @brief Checks if the pointer is not null.
explicit operator bool() const noexcept {
return p_obj != nullptr;
}
/// @brief Releases the pointer to the object. No destruction/deallocation happen after this function gets called.
/// @return The pointer to the object.
ObjT* release() noexcept {
ObjT* ret = p_obj;
p_obj = nullptr;
return ret;
}
/// @brief The pointer to the object.
ObjT* p_obj {nullptr};
};
/// @brief Allocates and constructs an `ObjT` object with given arguments.
/// @tparam ObjT The object type.
/// @tparam ...Args The argument types.
/// @param ...args The arguments for construction.
/// @return An address of allocated memory on the heap.
template <typename ObjT, typename... Args>
inline ObjT* create_object(Args&&... args) {
using alloc_type = std::allocator<ObjT>;
using alloc_traits_type = std::allocator_traits<alloc_type>;
alloc_type alloc {};
tidy_guard<ObjT, alloc_traits_type> tg {alloc_traits_type::allocate(alloc, 1)};
alloc_traits_type::construct(alloc, tg.get(), std::forward<Args>(args)...);
FK_YAML_ASSERT(tg);
return tg.release();
}
/// @brief Destroys and deallocates an `ObjT` object.
/// @tparam ObjT The object type.
/// @param p_obj A pointer to the object.
template <typename ObjT>
inline void destroy_object(ObjT* p_obj) {
FK_YAML_ASSERT(p_obj != nullptr);
std::allocator<ObjT> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, p_obj);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, p_obj, 1);
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_EXCEPTION_SAFE_ALLOCATION_HPP */
// #include <fkYAML/detail/input/deserializer.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_DESERIALIZER_HPP
#define FK_YAML_DETAIL_INPUT_DESERIALIZER_HPP
#include <algorithm>
#include <deque>
#include <vector>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/document_metainfo.hpp>
// #include <fkYAML/detail/input/lexical_analyzer.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_LEXICAL_ANALYZER_HPP
#define FK_YAML_DETAIL_INPUT_LEXICAL_ANALYZER_HPP
#include <algorithm>
#include <cctype>
#include <cstdlib>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/detail/encodings/uri_encoding.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ENCODINGS_URI_ENCODING_HPP
#define FK_YAML_DETAIL_ENCODINGS_URI_ENCODING_HPP
#include <cctype>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A class which handles URI encodings.
class uri_encoding {
public:
/// @brief Validates the encoding of the given character sequence.
/// @param begin An iterator to the first element of the character sequence.
/// @param end An iterator to the past-the-end element of the character sequence.
/// @return true if all the characters are valid, false otherwise.
static bool validate(const char* begin, const char* end) noexcept {
if (begin == end) {
return true;
}
const char* current = begin;
for (; current != end; ++current) {
if (*current == '%') {
const bool are_valid_octets = validate_octets(++current, end);
if (!are_valid_octets) {
return false;
}
continue;
}
const bool is_allowed_character = validate_character(*current);
if (!is_allowed_character) {
return false;
}
}
return true;
}
private:
/// @brief Validates the given octets.
/// @param begin An iterator to the first octet.
/// @param end An iterator to the past-the-end element of the whole character sequence.
/// @return true if the octets are valid, false otherwise.
static bool validate_octets(const char*& begin, const char*& end) {
for (int i = 0; i < 2; i++, ++begin) {
if (begin == end) {
return false;
}
// Normalize a character for a-f/A-F comparison
const int octet = std::tolower(*begin);
if ('0' <= octet && octet <= '9') {
continue;
}
if ('a' <= octet && octet <= 'f') {
continue;
}
return false;
}
return true;
}
/// @brief Verify if the given character is allowed as a URI character.
/// @param c The target character.
/// @return true if the given character is allowed as a URI character, false otherwise.
static bool validate_character(const char c) {
// Check if the current character is one of reserved/unreserved characters which are allowed for
// use. See the following links for details:
// * reserved characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
// * unreserved characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
switch (c) {
// reserved characters (gen-delims)
case ':':
case '/':
case '?':
case '#':
case '[':
case ']':
case '@':
// reserved characters (sub-delims)
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
// unreserved characters
case '-':
case '.':
case '_':
case '~':
return true;
default:
// alphabets and numbers are also allowed.
return static_cast<bool>(std::isalnum(c));
}
}
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_ENCODINGS_URI_ENCODING_HPP */
// #include <fkYAML/detail/encodings/utf_encodings.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODINGS_HPP
#define FK_YAML_DETAIL_ENCODINGS_UTF_ENCODINGS_HPP
#include <array>
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/exception.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_EXCEPTION_HPP
#define FK_YAML_EXCEPTION_HPP
#include <array>
#include <initializer_list>
#include <stdexcept>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/string_formatter.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_STRING_FORMATTER_HPP
#define FK_YAML_DETAIL_STRING_FORMATTER_HPP
#include <cstdarg>
#include <cstdio>
#include <memory>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
// NOLINTNEXTLINE(cert-dcl50-cpp)
inline std::string format(const char* fmt, ...) {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
va_list vl;
va_start(vl, fmt);
int size = std::vsnprintf(nullptr, 0, fmt, vl);
va_end(vl);
// LCOV_EXCL_START
if (size < 0) {
return "";
}
// LCOV_EXCL_STOP
const std::unique_ptr<char[]> buffer {new char[size + 1] {}};
va_start(vl, fmt);
size = std::vsnprintf(buffer.get(), size + 1, fmt, vl);
va_end(vl);
// NOLINTEND(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
return {buffer.get(), static_cast<std::size_t>(size)};
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_STRING_FORMATTER_HPP */
// #include <fkYAML/detail/types/node_t.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_TYPES_NODE_T_HPP
#define FK_YAML_DETAIL_TYPES_NODE_T_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/node_type.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_NODE_TYPE_HPP
#define FK_YAML_NODE_TYPE_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
FK_YAML_NAMESPACE_BEGIN
enum class node_type : std::uint8_t {
SEQUENCE, //!< sequence value type
MAPPING, //!< mapping value type
NULL_OBJECT, //!< null value type
BOOLEAN, //!< boolean value type
INTEGER, //!< integer value type
FLOAT, //!< float point value type
STRING, //!< string value type
};
inline const char* to_string(node_type t) noexcept {
switch (t) {
case node_type::SEQUENCE:
return "SEQUENCE";
case node_type::MAPPING:
return "MAPPING";
case node_type::NULL_OBJECT:
return "NULL_OBJECT";
case node_type::BOOLEAN:
return "BOOLEAN";
case node_type::INTEGER:
return "INTEGER";
case node_type::FLOAT:
return "FLOAT";
case node_type::STRING:
return "STRING";
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_NODE_TYPE_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Definition of node value types.
enum class node_t : std::uint8_t {
SEQUENCE, //!< sequence value type
MAPPING, //!< mapping value type
NULL_OBJECT, //!< null value type
BOOLEAN, //!< boolean value type
INTEGER, //!< integer value type
FLOAT_NUMBER, //!< float number value type
STRING, //!< string value type
};
inline const char* to_string(node_t t) noexcept {
switch (t) {
case node_t::SEQUENCE:
return "sequence";
case node_t::MAPPING:
return "mapping";
case node_t::NULL_OBJECT:
return "null";
case node_t::BOOLEAN:
return "boolean";
case node_t::INTEGER:
return "integer";
case node_t::FLOAT_NUMBER:
return "float";
case node_t::STRING:
return "string";
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
inline node_t convert_from_node_type(node_type t) {
switch (t) {
case node_type::SEQUENCE:
return node_t::SEQUENCE;
case node_type::MAPPING:
return node_t::MAPPING;
case node_type::NULL_OBJECT:
return node_t::NULL_OBJECT;
case node_type::BOOLEAN:
return node_t::BOOLEAN;
case node_type::INTEGER:
return node_t::INTEGER;
case node_type::FLOAT:
return node_t::FLOAT_NUMBER;
case node_type::STRING:
return node_t::STRING;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
inline node_type convert_to_node_type(node_t t) {
switch (t) {
case node_t::SEQUENCE:
return node_type::SEQUENCE;
case node_t::MAPPING:
return node_type::MAPPING;
case node_t::NULL_OBJECT:
return node_type::NULL_OBJECT;
case node_t::BOOLEAN:
return node_type::BOOLEAN;
case node_t::INTEGER:
return node_type::INTEGER;
case node_t::FLOAT_NUMBER:
return node_type::FLOAT;
case node_t::STRING:
return node_type::STRING;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_TYPES_NODE_T_HPP */
FK_YAML_NAMESPACE_BEGIN
/// @brief A base exception class used in fkYAML library.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/
class exception : public std::exception {
public:
/// @brief Construct a new exception object without any error messages.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/constructor/
exception() = default;
/// @brief Construct a new exception object with an error message.
/// @param[in] msg An error message.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/constructor/
explicit exception(const char* msg) noexcept {
if (msg) {
m_error_msg = msg;
}
}
public:
/// @brief Returns an error message internally held. If nothing, a non-null, empty string will be returned.
/// @return An error message internally held. The message might be empty.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/what/
const char* what() const noexcept override {
return m_error_msg.c_str();
}
private:
/// An error message holder.
std::string m_error_msg;
};
/// @brief An exception class indicating an encoding error.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/invalid_encoding/
class invalid_encoding : public exception {
public:
/// @brief Construct a new invalid_encoding object for UTF-8 related errors.
/// @param msg An error message.
/// @param u8 The UTF-8 character bytes.
explicit invalid_encoding(const char* msg, const std::initializer_list<uint8_t>& u8) noexcept
: exception(generate_error_message(msg, u8).c_str()) {
}
/// @brief Construct a new invalid_encoding object for UTF-16 related errors.
/// @param msg An error message.
/// @param u16_h The first UTF-16 encoded element used for the UTF-8 encoding.
/// @param u16_l The second UTF-16 encoded element used for the UTF-8 encoding.
explicit invalid_encoding(const char* msg, std::array<char16_t, 2> u16) noexcept
: exception(generate_error_message(msg, u16).c_str()) {
}
/// @brief Construct a new invalid_encoding object for UTF-32 related errors.
/// @param msg An error message.
/// @param u32 The UTF-32 encoded element used for the UTF-8 encoding.
explicit invalid_encoding(const char* msg, char32_t u32) noexcept
: exception(generate_error_message(msg, u32).c_str()) {
}
private:
static std::string generate_error_message(const char* msg, const std::initializer_list<uint8_t>& u8) noexcept {
const auto* itr = u8.begin();
const auto* end_itr = u8.end();
std::string formatted = detail::format("invalid_encoding: %s in=[ 0x%02x", msg, *itr++);
while (itr != end_itr) {
formatted += detail::format(", 0x%02x", *itr++);
}
formatted += " ]";
return formatted;
}
/// @brief Generate an error message from the given parameters for the UTF-16 encoding.
/// @param msg An error message.
/// @param h The first UTF-16 encoded element used for the UTF-8 encoding.
/// @param l The second UTF-16 encoded element used for the UTF-8 encoding.
/// @return A generated error message.
static std::string generate_error_message(const char* msg, std::array<char16_t, 2> u16) noexcept {
// uint16_t is large enough for UTF-16 encoded elements.
return detail::format(
"invalid_encoding: %s in=[ 0x%04x, 0x%04x ]",
msg,
static_cast<uint16_t>(u16[0]),
static_cast<uint16_t>(u16[1]));
}
/// @brief Generate an error message from the given parameters for the UTF-32 encoding.
/// @param msg An error message.
/// @param u32 The UTF-32 encoded element used for the UTF-8 encoding.
/// @return A generated error message.
static std::string generate_error_message(const char* msg, char32_t u32) noexcept {
// uint32_t is large enough for UTF-32 encoded elements.
return detail::format("invalid_encoding: %s in=0x%08x", msg, static_cast<uint32_t>(u32));
}
};
/// @brief An exception class indicating an error in parsing.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/parse_error/
class parse_error : public exception {
public:
/// @brief Constructs a new parse_error object with an error message and counts of lines and columns at the error.
/// @param[in] msg An error message.
/// @param[in] lines Count of lines.
/// @param[in] cols_in_line Count of columns.
explicit parse_error(const char* msg, uint32_t lines, uint32_t cols_in_line) noexcept
: exception(generate_error_message(msg, lines, cols_in_line).c_str()) {
}
private:
static std::string generate_error_message(const char* msg, uint32_t lines, uint32_t cols_in_line) noexcept {
return detail::format("parse_error: %s (at line %u, column %u)", msg, lines, cols_in_line);
}
};
/// @brief An exception class indicating an invalid type conversion.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/type_error/
class type_error : public exception {
public:
/// @brief Construct a new type_error object with an error message and a node type.
/// @param[in] msg An error message.
/// @param[in] type The type of a source node value.
explicit type_error(const char* msg, node_type type) noexcept
: exception(generate_error_message(msg, type).c_str()) {
}
/// @brief Construct a new type_error object with an error message and a node type.
/// @deprecated Use type_error(const char*, node_type) constructor. (since 0.3.12).
/// @param[in] msg An error message.
/// @param[in] type The type of a source node value.
FK_YAML_DEPRECATED("Since 0.3.12; Use explicit type_error(const char*, node_type)")
explicit type_error(const char* msg, detail::node_t type) noexcept
: type_error(msg, detail::convert_to_node_type(type)) {
}
private:
/// @brief Generate an error message from given parameters.
/// @param msg An error message.
/// @param type The type of a source node value.
/// @return A generated error message.
static std::string generate_error_message(const char* msg, node_type type) noexcept {
return detail::format("type_error: %s type=%s", msg, to_string(type));
}
};
/// @brief An exception class indicating an out-of-range error.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/out_of_range/
class out_of_range : public exception {
public:
/// @brief Construct a new out_of_range object with an invalid index value.
/// @param[in] index An invalid index value.
explicit out_of_range(int index) noexcept
: exception(generate_error_message(index).c_str()) {
}
/// @brief Construct a new out_of_range object with invalid key contents.
/// @param[in] key Invalid key contents
explicit out_of_range(const char* key) noexcept
: exception(generate_error_message(key).c_str()) {
}
private:
static std::string generate_error_message(int index) noexcept {
return detail::format("out_of_range: index %d is out of range", index);
}
static std::string generate_error_message(const char* key) noexcept {
return detail::format("out_of_range: key \'%s\' is not found.", key);
}
};
/// @brief An exception class indicating an invalid tag.
/// @sa https://fktn-k.github.io/fkYAML/api/exception/invalid_tag/
class invalid_tag : public exception {
public:
/// @brief Constructs a new invalid_tag object with an error message and invalid tag contents.
/// @param[in] msg An error message.
/// @param[in] tag Invalid tag contents.
explicit invalid_tag(const char* msg, const char* tag)
: exception(generate_error_message(msg, tag).c_str()) {
}
private:
static std::string generate_error_message(const char* msg, const char* tag) noexcept {
return detail::format("invalid_tag: %s tag=%s", msg, tag);
}
};
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_EXCEPTION_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/////////////////////////
// UTF-8 Encoding ///
/////////////////////////
/// @brief A class which handles UTF-8 encodings.
namespace utf8 {
/// @brief Query the number of UTF-8 character bytes with the first byte.
/// @param first_byte The first byte of a UTF-8 character.
/// @return The number of UTF-8 character bytes.
inline uint32_t get_num_bytes(uint8_t first_byte) {
// The first byte starts with 0b0XXX'XXXX -> 1-byte character
if FK_YAML_LIKELY (first_byte < 0x80) {
return 1;
}
// The first byte starts with 0b110X'XXXX -> 2-byte character
if ((first_byte & 0xE0) == 0xC0) {
return 2;
}
// The first byte starts with 0b1110'XXXX -> 3-byte character
if ((first_byte & 0xF0) == 0xE0) {
return 3;
}
// The first byte starts with 0b1111'0XXX -> 4-byte character
if ((first_byte & 0xF8) == 0xF0) {
return 4;
}
// The first byte starts with 0b10XX'XXXX or 0b1111'1XXX -> invalid
throw fkyaml::invalid_encoding("Invalid UTF-8 encoding.", {first_byte});
}
/// @brief Checks if `byte` is a valid 1-byte UTF-8 character.
/// @param[in] byte The byte value.
/// @return true if `byte` is a valid 1-byte UTF-8 character, false otherwise.
inline bool validate(uint8_t byte) noexcept {
// U+0000..U+007F
return byte <= 0x7Fu;
}
/// @brief Checks if the given bytes are a valid 2-byte UTF-8 character.
/// @param[in] byte0 The first byte value.
/// @param[in] byte1 The second byte value.
/// @return true if the given bytes a valid 3-byte UTF-8 character, false otherwise.
inline bool validate(uint8_t byte0, uint8_t byte1) noexcept {
// U+0080..U+07FF
// 1st Byte: 0xC2..0xDF
// 2nd Byte: 0x80..0xBF
if FK_YAML_LIKELY (0xC2u <= byte0 && byte0 <= 0xDFu) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0xBFu) {
return true;
}
}
// The rest of byte combinations are invalid.
return false;
}
/// @brief Checks if the given bytes are a valid 3-byte UTF-8 character.
/// @param[in] byte0 The first byte value.
/// @param[in] byte1 The second byte value.
/// @param[in] byte2 The third byte value.
/// @return true if the given bytes a valid 2-byte UTF-8 character, false otherwise.
inline bool validate(uint8_t byte0, uint8_t byte1, uint8_t byte2) noexcept {
// U+1000..U+CFFF:
// 1st Byte: 0xE0..0xEC
// 2nd Byte: 0x80..0xBF
// 3rd Byte: 0x80..0xBF
if (0xE0u <= byte0 && byte0 <= 0xECu) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
return true;
}
}
return false;
}
// U+D000..U+D7FF:
// 1st Byte: 0xED
// 2nd Byte: 0x80..0x9F
// 3rd Byte: 0x80..0xBF
if (byte0 == 0xEDu) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0x9Fu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
return true;
}
}
return false;
}
// U+E000..U+FFFF:
// 1st Byte: 0xEE..0xEF
// 2nd Byte: 0x80..0xBF
// 3rd Byte: 0x80..0xBF
if FK_YAML_LIKELY (byte0 == 0xEEu || byte0 == 0xEFu) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
return true;
}
}
}
// The rest of byte combinations are invalid.
return false;
}
/// @brief Checks if the given bytes are a valid 4-byte UTF-8 character.
/// @param[in] byte0 The first byte value.
/// @param[in] byte1 The second byte value.
/// @param[in] byte2 The third byte value.
/// @param[in] byte3 The fourth byte value.
/// @return true if the given bytes a valid 4-byte UTF-8 character, false otherwise.
inline bool validate(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept {
// U+10000..U+3FFFF:
// 1st Byte: 0xF0
// 2nd Byte: 0x90..0xBF
// 3rd Byte: 0x80..0xBF
// 4th Byte: 0x80..0xBF
if (byte0 == 0xF0u) {
if FK_YAML_LIKELY (0x90u <= byte1 && byte1 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte3 && byte3 <= 0xBFu) {
return true;
}
}
}
return false;
}
// U+40000..U+FFFFF:
// 1st Byte: 0xF1..0xF3
// 2nd Byte: 0x80..0xBF
// 3rd Byte: 0x80..0xBF
// 4th Byte: 0x80..0xBF
if (0xF1u <= byte0 && byte0 <= 0xF3u) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte3 && byte3 <= 0xBFu) {
return true;
}
}
}
return false;
}
// U+100000..U+10FFFF:
// 1st Byte: 0xF4
// 2nd Byte: 0x80..0x8F
// 3rd Byte: 0x80..0xBF
// 4th Byte: 0x80..0xBF
if FK_YAML_LIKELY (byte0 == 0xF4u) {
if FK_YAML_LIKELY (0x80u <= byte1 && byte1 <= 0x8Fu) {
if FK_YAML_LIKELY (0x80u <= byte2 && byte2 <= 0xBFu) {
if FK_YAML_LIKELY (0x80u <= byte3 && byte3 <= 0xBFu) {
return true;
}
}
}
}
// The rest of byte combinations are invalid.
return false;
}
/// @brief Converts UTF-16 encoded characters to UTF-8 encoded bytes.
/// @param[in] utf16 UTF-16 encoded character(s).
/// @param[out] utf8 UTF-8 encoded bytes.
/// @param[out] consumed_size The number of UTF-16 encoded characters used for the conversion.
/// @param[out] encoded_size The size of UTF-encoded bytes.
inline void from_utf16(
std::array<char16_t, 2> utf16, std::array<uint8_t, 4>& utf8, uint32_t& consumed_size, uint32_t& encoded_size) {
const auto first = utf16[0];
const auto second = utf16[1];
if (first < 0x80u) {
utf8[0] = static_cast<uint8_t>(first & 0x7Fu);
consumed_size = 1;
encoded_size = 1;
}
else if (first <= 0x7FFu) {
const auto utf8_chunk = static_cast<uint16_t>(0xC080u | ((first & 0x07C0u) << 2) | (first & 0x3Fu));
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[1] = static_cast<uint8_t>(utf8_chunk);
consumed_size = 1;
encoded_size = 2;
}
else if (first < 0xD800u || 0xE000u <= first) {
const uint32_t utf8_chunk = 0xE08080u | ((first & 0xF000u) << 4) | ((first & 0x0FC0u) << 2) | (first & 0x3Fu);
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 16);
utf8[1] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[2] = static_cast<uint8_t>(utf8_chunk);
consumed_size = 1;
encoded_size = 3;
}
else if (first <= 0xDBFFu && 0xDC00u <= second && second <= 0xDFFFu) {
// surrogate pair
const uint32_t code_point = 0x10000u + ((first & 0x03FFu) << 10) + (second & 0x03FFu);
const uint32_t utf8_chunk = 0xF0808080u | ((code_point & 0x1C0000u) << 6) | ((code_point & 0x03F000u) << 4) |
((code_point & 0x0FC0u) << 2) | (code_point & 0x3Fu);
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 24);
utf8[1] = static_cast<uint8_t>(utf8_chunk >> 16);
utf8[2] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[3] = static_cast<uint8_t>(utf8_chunk);
consumed_size = 2;
encoded_size = 4;
}
else {
throw invalid_encoding("Invalid UTF-16 encoding detected.", utf16);
}
}
/// @brief Converts a UTF-32 encoded character to UTF-8 encoded bytes.
/// @param[in] utf32 A UTF-32 encoded character.
/// @param[out] utf8 UTF-8 encoded bytes.
/// @param[in] encoded_size The size of UTF-encoded bytes.
inline void from_utf32(const char32_t utf32, std::array<uint8_t, 4>& utf8, uint32_t& encoded_size) {
if (utf32 < 0x80u) {
utf8[0] = static_cast<uint8_t>(utf32 & 0x007F);
encoded_size = 1;
}
else if (utf32 <= 0x7FFu) {
const auto utf8_chunk = static_cast<uint16_t>(0xC080u | ((utf32 & 0x07C0u) << 2) | (utf32 & 0x3Fu));
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[1] = static_cast<uint8_t>(utf8_chunk);
encoded_size = 2;
}
else if (utf32 <= 0xFFFFu) {
const uint32_t utf8_chunk = 0xE08080u | ((utf32 & 0xF000u) << 4) | ((utf32 & 0x0FC0u) << 2) | (utf32 & 0x3F);
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 16);
utf8[1] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[2] = static_cast<uint8_t>(utf8_chunk);
encoded_size = 3;
}
else if (utf32 <= 0x10FFFFu) {
const uint32_t utf8_chunk = 0xF0808080u | ((utf32 & 0x1C0000u) << 6) | ((utf32 & 0x03F000u) << 4) |
((utf32 & 0x0FC0u) << 2) | (utf32 & 0x3Fu);
utf8[0] = static_cast<uint8_t>(utf8_chunk >> 24);
utf8[1] = static_cast<uint8_t>(utf8_chunk >> 16);
utf8[2] = static_cast<uint8_t>(utf8_chunk >> 8);
utf8[3] = static_cast<uint8_t>(utf8_chunk);
encoded_size = 4;
}
else {
throw invalid_encoding("Invalid UTF-32 encoding detected.", utf32);
}
}
} // namespace utf8
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_ENCODINGS_UTF_ENCODINGS_HPP */
// #include <fkYAML/detail/input/block_scalar_header.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_BLOCK_SCALAR_HEADER_HPP
#define FK_YAML_DETAIL_INPUT_BLOCK_SCALAR_HEADER_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Definition of chomping indicator types.
enum class chomping_indicator_t : std::uint8_t {
STRIP, //!< excludes final line breaks and trailing empty lines indicated by `-`.
CLIP, //!< preserves final line breaks but excludes trailing empty lines. no indicator means this type.
KEEP, //!< preserves final line breaks and trailing empty lines indicated by `+`.
};
/// @brief Block scalar header information.
struct block_scalar_header {
/// Chomping indicator type.
chomping_indicator_t chomp {chomping_indicator_t::CLIP};
/// Content indentation level of a block scalar.
uint32_t indent {0};
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_BLOCK_SCALAR_HEADER_HPP */
// #include <fkYAML/detail/input/position_tracker.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_POSITION_TRACKER_HPP
#define FK_YAML_DETAIL_INPUT_POSITION_TRACKER_HPP
#include <algorithm>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/str_view.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_STR_VIEW_HPP
#define FK_YAML_DETAIL_STR_VIEW_HPP
#include <limits>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Non owning view into constant character sequence.
/// @note
/// This class is a minimal implementation of std::basic_string_view which has been available since C++17
/// but pretty useful and efficient for referencing/investigating character sequences.
/// @warning
/// This class intentionally omits a lot of value checks to improve efficiency. Necessary checks should be
/// made before calling this class' APIs for safety.
/// @tparam CharT Character type
/// @tparam Traits Character traits type which defaults to std::char_traits<CharT>.
template <typename CharT, typename Traits = std::char_traits<CharT>>
class basic_str_view {
static_assert(!std::is_array<CharT>::value, "CharT must not be an array type.");
static_assert(
std::is_trivial<CharT>::value && std::is_standard_layout<CharT>::value,
"CharT must be a trivial, standard layout type.");
static_assert(
std::is_same<CharT, typename Traits::char_type>::value, "CharT & Traits::char_type must be the same type.");
public:
/// Character traits type.
using traits_type = Traits;
/// Character type.
using value_type = CharT;
/// Pointer type to a character.
using pointer = value_type*;
/// Constant pointer type to a character.
using const_pointer = const value_type*;
/// Reference type to a character.
using reference = value_type&;
/// Constant reference type to a character.
using const_reference = const value_type&;
/// Constant iterator type to a character.
using const_iterator = const value_type*;
/// Iterator type to a character.
/// (Always constant since this class isn't meant to provide any mutating features.)
using iterator = const_iterator;
/// Constant reverse iterator type to a character.
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/// Reverse iterator type to a character.
/// (Always constant since this class isn't meant to provide any mutating features.)
using reverse_iterator = const_reverse_iterator;
/// Size type for character sequence sizes.
using size_type = std::size_t;
/// Difference type for distances between characters.
using difference_type = std::ptrdiff_t;
/// Invalid position value.
static constexpr size_type npos = static_cast<size_type>(-1);
/// Constructs a basic_str_view object.
constexpr basic_str_view() noexcept = default;
/// Destroys a basic_str_view object.
~basic_str_view() noexcept = default;
/// @brief Copy constructs a basic_str_view object.
/// @param _ A basic_str_view object to copy from.
constexpr basic_str_view(const basic_str_view&) noexcept = default;
/// @brief Move constructs a basic_str_view object.
/// @param _ A basic_str_view object to move from.
constexpr basic_str_view(basic_str_view&&) noexcept = default;
/// @brief Constructs a basic_str_view object from a pointer to a character sequence.
/// @note std::char_traits::length() is constexpr from C++17.
/// @param p_str A pointer to a character sequence. (Must be null-terminated, or an undefined behavior.)
template <
typename CharPtrT,
enable_if_t<
conjunction<
negation<std::is_array<CharPtrT>>, std::is_pointer<CharPtrT>,
disjunction<std::is_same<CharPtrT, value_type*>, std::is_same<CharPtrT, const value_type*>>>::value,
int> = 0>
FK_YAML_CXX17_CONSTEXPR basic_str_view(CharPtrT p_str) noexcept
: m_len(traits_type::length(p_str)),
mp_str(p_str) {
}
/// @brief Constructs a basic_str_view object from a C-style char array.
/// @note
/// This constructor assumes the last element is the null character ('\0'). If that's not desirable, consider using
/// one of the other overloads.
/// @tparam N The size of a C-style char array.
/// @param str A C-style char array. (Must be null-terminated)
template <std::size_t N>
constexpr basic_str_view(const value_type (&str)[N]) noexcept
: m_len(N - 1),
mp_str(&str[0]) {
}
/// @brief Construction from a null pointer is forbidden.
basic_str_view(std::nullptr_t) = delete;
/// @brief Constructs a basic_str_view object from a pointer to a character sequence and its size.
/// @param p_str A pointer to a character sequence. (May or may not be null-terminated.)
/// @param len The length of a character sequence.
constexpr basic_str_view(const value_type* p_str, size_type len) noexcept
: m_len(len),
mp_str(p_str) {
}
/// @brief Constructs a basic_str_view object from compatible begin/end iterators
/// @tparam ItrType Iterator type to a character.
/// @param first The iterator to the first element of a character sequence.
/// @param last The iterator to the past-the-end of a character sequence.
template <
typename ItrType,
enable_if_t<
conjunction<
is_iterator_of<ItrType, CharT>,
std::is_base_of<
std::random_access_iterator_tag, typename std::iterator_traits<ItrType>::iterator_category>>::value,
int> = 0>
basic_str_view(ItrType first, ItrType last) noexcept
: m_len(last - first),
mp_str(&*first) {
}
/// @brief Constructs a basic_str_view object from a compatible std::basic_string object.
/// @param str A compatible character sequence container.
basic_str_view(const std::basic_string<CharT>& str) noexcept
: m_len(str.length()),
mp_str(str.data()) {
}
/// @brief Copy assignment operator for this basic_str_view class.
/// @param _ A basic_str_view object to copy from.
/// @return Reference to this basic_str_view object.
basic_str_view& operator=(const basic_str_view&) noexcept = default;
/// @brief Move assignment operator for this basic_str_view class.
/// @param _ A basic_str_view object to move from.
/// @return Reference to this basic_str_view object.
basic_str_view& operator=(basic_str_view&&) noexcept = default;
/// @brief Get the iterator to the first element. (Always constant)
/// @return The iterator to the first element.
const_iterator begin() const noexcept {
return mp_str;
}
/// @brief Get the iterator to the past-the-end element. (Always constant)
/// @return The iterator to the past-the-end element.
const_iterator end() const noexcept {
return mp_str + m_len;
}
/// @brief Get the iterator to the first element. (Always constant)
/// @return The iterator to the first element.
const_iterator cbegin() const noexcept {
return mp_str;
}
/// @brief Get the iterator to the past-the-end element. (Always constant)
/// @return The iterator to the past-the-end element.
const_iterator cend() const noexcept {
return mp_str + m_len;
}
/// @brief Get the iterator to the first element in the reverse order. (Always constant)
/// @return The iterator to the first element in the reverse order.
const_reverse_iterator rbegin() const noexcept {
return const_reverse_iterator(end());
}
/// @brief Get the iterator to the past-the-end element in the reverse order. (Always constant)
/// @return The iterator to the past-the-end element in the reverse order.
const_reverse_iterator rend() const noexcept {
return const_reverse_iterator(begin());
}
/// @brief Get the iterator to the first element in the reverse order. (Always constant)
/// @return The iterator to the first element in the reverse order.
const_reverse_iterator crbegin() const noexcept {
return const_reverse_iterator(end());
}
/// @brief Get the iterator to the past-the-end element in the reverse order. (Always constant)
/// @return The iterator to the past-the-end element in the reverse order.
const_reverse_iterator crend() const noexcept {
return const_reverse_iterator(begin());
}
/// @brief Get the size of the referenced character sequence.
/// @return The size of the referenced character sequence.
size_type size() const noexcept {
return m_len;
}
/// @brief Get the size of the referenced character sequence.
/// @return The size of the referenced character sequence.
size_type length() const noexcept {
return m_len;
}
/// @brief Get the maximum number of the character sequence size.
/// @return The maximum number of the character sequence size.
constexpr size_type max_size() const noexcept {
return static_cast<size_type>(std::numeric_limits<difference_type>::max());
}
/// @brief Checks if the referenced character sequence is empty.
/// @return true if empty, false otherwise.
bool empty() const noexcept {
return m_len == 0;
}
/// @brief Get the element at the given position.
/// @param pos The position of the target element.
/// @return The element at the given position.
const_reference operator[](size_type pos) const noexcept {
return *(mp_str + pos);
}
/// @brief Get the element at the given position with bounds checks.
/// @warning Throws an fkyaml::out_of_range exception if the position exceeds the character sequence size.
/// @param pos The position of the target element.
/// @return The element at the given position.
const_reference at(size_type pos) const {
if FK_YAML_UNLIKELY (pos >= m_len) {
throw fkyaml::out_of_range(static_cast<int>(pos));
}
return *(mp_str + pos);
}
/// @brief Get the first element.
/// @return The first element.
const_reference front() const noexcept {
return *mp_str;
}
/// @brief Get the last element.
/// @return The last element.
const_reference back() const {
return *(mp_str + m_len - 1);
}
/// @brief Get the pointer to the raw data of referenced character sequence.
/// @return The pointer to the raw data of referenced character sequence.
const_pointer data() const noexcept {
return mp_str;
}
/// @brief Moves the beginning position by `n` elements.
/// @param n The number of elements by which to move the beginning position.
void remove_prefix(size_type n) noexcept {
mp_str += n;
m_len -= n;
}
/// @brief Shrinks the referenced character sequence from the last by `n` elements.
/// @param n The number of elements by which to shrink the sequence from the last.
void remove_suffix(size_type n) noexcept {
m_len -= n;
}
/// @brief Swaps data with the given basic_str_view object.
/// @param other A basic_str_view object to swap data with.
void swap(basic_str_view& other) noexcept {
auto tmp = *this;
*this = other;
other = tmp;
}
/// @brief Copys the referenced character sequence values from `pos` by `n` size.
/// @warning Throws an fkyaml::out_of_range exception if the given `pos` is bigger than the length.
/// @param p_str The pointer to a character sequence buffer for output.
/// @param n The number of elements to write into `p_str`.
/// @param pos The offset of the beginning position to copy values.
/// @return The number of elements to be written into `p_str`.
size_type copy(CharT* p_str, size_type n, size_type pos = 0) const {
if FK_YAML_UNLIKELY (pos > m_len) {
throw fkyaml::out_of_range(static_cast<int>(pos));
}
const size_type rlen = std::min(n, m_len - pos);
traits_type::copy(p_str, mp_str + pos, rlen);
return rlen;
}
/// @brief Constructs a sub basic_str_view object from `pos` by `n` size.
/// @warning Throws an fkyaml::out_of_range exception if the given `pos` is bigger than the length.
/// @param pos The offset of the beginning position.
/// @param n The number of elements to the end of a new sub basic_str_view object.
/// @return A newly created sub basic_str_view object.
basic_str_view substr(size_type pos = 0, size_type n = npos) const {
if FK_YAML_UNLIKELY (pos > m_len) {
throw fkyaml::out_of_range(static_cast<int>(pos));
}
const size_type rlen = std::min(n, m_len - pos);
return basic_str_view(mp_str + pos, rlen);
}
/// @brief Compares the referenced character sequence values with the given basic_str_view object.
/// @param sv The basic_str_view object to compare with.
/// @return The lexicographical comparison result. The values are same as std::strncmp().
int compare(basic_str_view sv) const noexcept {
const size_type rlen = std::min(m_len, sv.m_len);
int ret = traits_type::compare(mp_str, sv.mp_str, rlen);
if (ret == 0) {
using int_limits = std::numeric_limits<int>;
const difference_type diff =
m_len > sv.m_len ? m_len - sv.m_len
: static_cast<difference_type>(-1) * static_cast<difference_type>(sv.m_len - m_len);
if (diff > int_limits::max()) {
ret = int_limits::max();
}
else if (diff < int_limits::min()) {
ret = int_limits::min();
}
else {
ret = static_cast<int>(diff);
}
}
return ret;
}
/// @brief Compares the referenced character sequence values from `pos1` by `n1` characters with `sv`.
/// @param pos1 The offset of the beginning element.
/// @param n1 The length of character sequence used for comparison.
/// @param sv A basic_str_view object to compare with.
/// @return The lexicographical comparison result. The values are same as std::strncmp().
int compare(size_type pos1, size_type n1, basic_str_view sv) const {
return substr(pos1, n1).compare(sv);
}
/// @brief Compares the referenced character sequence value from `pos1` by `n1` characters with `sv` from `pos2` by
/// `n2` characters.
/// @param pos1 The offset of the beginning element in this character sequence.
/// @param n1 The length of this character sequence used for comparison.
/// @param sv A basic_str_view object to compare with.
/// @param pos2 The offset of the beginning element in `sv`.
/// @param n2 The length of `sv` used for comparison.
/// @return The lexicographical comparison result. The values are same as std::strncmp().
int compare(size_type pos1, size_type n1, basic_str_view sv, size_type pos2, size_type n2) const {
return substr(pos1, n1).compare(sv.substr(pos2, n2));
}
/// @brief Compares the referenced character sequence with `s` character sequence.
/// @param s The pointer to a character sequence to compare with.
/// @return The lexicographical comparison result. The values are same as std::strncmp().
int compare(const CharT* s) const {
return compare(basic_str_view(s));
}
/// @brief Compares the referenced character sequence from `pos1` by `n1` characters with `s` character sequence.
/// @param pos1 The offset of the beginning element in this character sequence.
/// @param n1 The length of this character sequence used fo comparison.
/// @param s The pointer to a character sequence to compare with.
/// @return The lexicographical comparison result. The values are same as std::strncmp().
int compare(size_type pos1, size_type n1, const CharT* s) const {
return substr(pos1, n1).compare(basic_str_view(s));
}
/// @brief Compares the referenced character sequence from `pos1` by `n1` characters with `s` character sequence by
/// `n2` characters.
/// @param pos1 The offset of the beginning element in this character sequence.
/// @param n1 The length of this character sequence used fo comparison.
/// @param s The pointer to a character sequence to compare with.
/// @param n2 The length of `s` used fo comparison.
/// @return
int compare(size_type pos1, size_type n1, const CharT* s, size_type n2) const {
return substr(pos1, n1).compare(basic_str_view(s, n2));
}
/// @brief Checks if this character sequence starts with `sv` characters.
/// @param sv The character sequence to compare with.
/// @return true if the character sequence starts with `sv` characters, false otherwise.
bool starts_with(basic_str_view sv) const {
return substr(0, sv.size()) == sv;
}
/// @brief Checks if this character sequence starts with `c` character.
/// @param c The character to compare with.
/// @return true if the character sequence starts with `c` character, false otherwise.
bool starts_with(CharT c) const noexcept {
return !empty() && traits_type::eq(front(), c);
}
/// @brief Checks if this character sequence starts with `s` characters.
/// @param s The character sequence to compare with.
/// @return true if the character sequence starts with `s` characters, false otherwise.
bool starts_with(const CharT* s) const {
return starts_with(basic_str_view(s));
}
/// @brief Checks if this character sequence ends with `sv` characters.
/// @param sv The character sequence to compare with.
/// @return true if the character sequence ends with `sv` characters, false otherwise.
bool ends_with(basic_str_view sv) const noexcept {
const size_type size = m_len;
const size_type sv_size = sv.size();
return size >= sv_size && traits_type::compare(end() - sv_size, sv.data(), sv_size) == 0;
}
/// @brief Checks if this character sequence ends with `c` character.
/// @param c The character to compare with.
/// @return true if the character sequence ends with `c` character, false otherwise.
bool ends_with(CharT c) const noexcept {
return !empty() && traits_type::eq(back(), c);
}
/// @brief Checks if this character sequence ends with `s` characters.
/// @param s The character sequence to compare with.
/// @return true if the character sequence ends with `s` characters, false otherwise.
bool ends_with(const CharT* s) const noexcept {
return ends_with(basic_str_view(s));
}
/// @brief Checks if this character sequence contains `sv` characters.
/// @param sv The character sequence to compare with.
/// @return true if the character sequence contains `sv` characters, false otherwise.
bool contains(basic_str_view sv) const noexcept {
return find(sv) != npos;
}
/// @brief Checks if this character sequence contains `c` character.
/// @param c The character to compare with.
/// @return true if the character sequence contains `c` character, false otherwise.
bool contains(CharT c) const noexcept {
return find(c) != npos;
}
/// @brief Checks if this character sequence contains `s` characters.
/// @param s The character sequence to compare with.
/// @return true if the character sequence contains `s` characters, false otherwise.
bool contains(const CharT* s) const noexcept {
return find(s) != npos;
}
/// @brief Finds the beginning position of `sv` characters in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `sv` characters, `npos` otherwise.
size_type find(basic_str_view sv, size_type pos = 0) const noexcept {
return find(sv.mp_str, pos, sv.m_len);
}
/// @brief Finds the beginning position of `c` character in this referenced character sequence.
/// @param sv The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `c` character, `npos` otherwise.
size_type find(CharT c, size_type pos = 0) const noexcept {
size_type ret = npos;
if FK_YAML_LIKELY (pos < m_len) {
const size_type n = m_len - pos;
const CharT* p_found = traits_type::find(mp_str + pos, n, c);
if (p_found) {
ret = p_found - mp_str;
}
}
return ret;
}
/// @brief Finds the beginning position of `s` character sequence by `n` characters in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find(const CharT* s, size_type pos, size_type n) const noexcept {
if FK_YAML_UNLIKELY (n == 0) {
return pos <= m_len ? pos : npos;
}
if FK_YAML_UNLIKELY (pos >= m_len) {
return npos;
}
CharT s0 = s[0];
const CharT* p_first = mp_str + pos;
const CharT* p_last = mp_str + m_len;
size_type len = m_len - pos;
while (len >= n) {
// find the first occurrence of s0
p_first = traits_type::find(p_first, len - n + 1, s0);
if (!p_first) {
return npos;
}
// compare the full strings from the first occurrence of s0
if (traits_type::compare(p_first, s, n) == 0) {
return p_first - mp_str;
}
len = p_last - (++p_first);
}
return npos;
}
/// @brief Finds the beginning position of `s` character sequence in this referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find(const CharT* s, size_type pos = 0) const noexcept {
return find(basic_str_view(s), pos);
}
/// @brief Retrospectively finds the beginning position of `sv` characters in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `sv` characters, `npos` otherwise.
size_type rfind(basic_str_view sv, size_type pos = npos) const noexcept {
return rfind(sv.mp_str, pos, sv.m_len);
}
/// @brief Retrospectively finds the beginning position of `c` character in this referenced character sequence.
/// @param sv The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `c` character, `npos` otherwise.
size_type rfind(CharT c, size_type pos = npos) const noexcept {
if FK_YAML_UNLIKELY (m_len == 0) {
return npos;
}
const size_type idx = std::min(m_len - 1, pos);
for (size_type i = 0; i <= idx; i++) {
if (traits_type::eq(mp_str[idx - i], c)) {
return idx - i;
}
}
return npos;
}
/// @brief Retrospectively finds the beginning position of `s` character sequence by `n` characters in this
/// referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type rfind(const CharT* s, size_type pos, size_type n) const noexcept {
if FK_YAML_LIKELY (n <= m_len) {
pos = std::min(m_len - n, pos) + 1;
do {
if (traits_type::compare(mp_str + --pos, s, n) == 0) {
return pos;
}
} while (pos > 0);
}
return npos;
}
/// @brief Retrospectively finds the beginning position of `s` character sequence in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type rfind(const CharT* s, size_type pos = npos) const noexcept {
return rfind(basic_str_view(s), pos);
}
/// @brief Finds the first occurrence of `sv` character sequence in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `sv` characters, `npos` otherwise.
size_type find_first_of(basic_str_view sv, size_type pos = 0) const noexcept {
return find_first_of(sv.mp_str, pos, sv.m_len);
}
/// @brief Finds the first occurrence of `c` character in this referenced character sequence.
/// @param c The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `c` character, `npos` otherwise.
size_type find_first_of(CharT c, size_type pos = 0) const noexcept {
return find(c, pos);
}
/// @brief Finds the first occurrence of `s` character sequence by `n` characters in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find_first_of(const CharT* s, size_type pos, size_type n) const noexcept {
if FK_YAML_UNLIKELY (n == 0) {
return npos;
}
for (size_type idx = pos; idx < m_len; ++idx) {
const CharT* p_found = traits_type::find(s, n, mp_str[idx]);
if (p_found) {
return idx;
}
}
return npos;
}
/// @brief Finds the first occurrence of `s` character sequence in this referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find_first_of(const CharT* s, size_type pos = 0) const noexcept {
return find_first_of(basic_str_view(s), pos);
}
/// @brief Finds the last occurrence of `sv` character sequence in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `sv` characters, `npos` otherwise.
size_type find_last_of(basic_str_view sv, size_type pos = npos) const noexcept {
return find_last_of(sv.mp_str, pos, sv.m_len);
}
/// @brief Finds the last occurrence of `c` character in this referenced character sequence.
/// @param c The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `c` character, `npos` otherwise.
size_type find_last_of(CharT c, size_type pos = npos) const noexcept {
return rfind(c, pos);
}
/// @brief Finds the last occurrence of `s` character sequence by `n` characters in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find_last_of(const CharT* s, size_type pos, size_type n) const noexcept {
if FK_YAML_LIKELY (n <= m_len) {
pos = std::min(m_len - n - 1, pos);
do {
const CharT* p_found = traits_type::find(s, n, mp_str[pos]);
if (p_found) {
return pos;
}
} while (pos-- != 0);
}
return npos;
}
/// @brief Finds the last occurrence of `s` character sequence in this referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of `s` characters, `npos` otherwise.
size_type find_last_of(const CharT* s, size_type pos = npos) const noexcept {
return find_last_of(basic_str_view(s), pos);
}
/// @brief Finds the first absence of `sv` character sequence in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `sv` characters, `npos` otherwise.
size_type find_first_not_of(basic_str_view sv, size_type pos = 0) const noexcept {
return find_first_not_of(sv.mp_str, pos, sv.m_len);
}
/// @brief Finds the first absence of `c` character in this referenced character sequence.
/// @param c The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `c` character, `npos` otherwise.
size_type find_first_not_of(CharT c, size_type pos = 0) const noexcept {
for (; pos < m_len; ++pos) {
if (!traits_type::eq(mp_str[pos], c)) {
return pos;
}
}
return npos;
}
/// @brief Finds the first absence of `s` character sequence by `n` characters in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of non `s` characters, `npos` otherwise.
size_type find_first_not_of(const CharT* s, size_type pos, size_type n) const noexcept {
for (; pos < m_len; ++pos) {
const CharT* p_found = traits_type::find(s, n, mp_str[pos]);
if (!p_found) {
return pos;
}
}
return npos;
}
/// @brief Finds the first absence of `s` character sequence in this referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `s` characters, `npos` otherwise.
size_type find_first_not_of(const CharT* s, size_type pos = 0) const noexcept {
return find_first_not_of(basic_str_view(s), pos);
}
/// @brief Finds the last absence of `sv` character sequence in this referenced character sequence.
/// @param sv The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `sv` characters, `npos` otherwise.
size_type find_last_not_of(basic_str_view sv, size_type pos = npos) const noexcept {
return find_last_not_of(sv.mp_str, pos, sv.m_len);
}
/// @brief Finds the last absence of `c` character in this referenced character sequence.
/// @param c The character to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `c` character, `npos` otherwise.
size_type find_last_not_of(CharT c, size_type pos = npos) const noexcept {
if FK_YAML_LIKELY (m_len > 0) {
pos = std::min(m_len, pos);
do {
if (!traits_type::eq(mp_str[--pos], c)) {
return pos;
}
} while (pos > 0);
}
return npos;
}
/// @brief Finds the last absence of `s` character sequence by `n` characters in this referenced character
/// sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @param n The length of `s` character sequence used for comparison.
/// @return The beginning position of non `s` characters, `npos` otherwise.
size_type find_last_not_of(const CharT* s, size_type pos, size_type n) const noexcept {
if FK_YAML_UNLIKELY (n <= m_len) {
pos = std::min(m_len - n, pos) + 1;
do {
const CharT* p_found = traits_type::find(s, n, mp_str[--pos]);
if (!p_found) {
return pos;
}
} while (pos > 0);
}
return npos;
}
/// @brief Finds the last absence of `s` character sequence in this referenced character sequence.
/// @param s The character sequence to compare with.
/// @param pos The offset of the search beginning position in this referenced character sequence.
/// @return The beginning position of non `s` characters, `npos` otherwise.
size_type find_last_not_of(const CharT* s, size_type pos = npos) const noexcept {
return find_last_not_of(basic_str_view(s), pos);
}
private:
size_type m_len {0};
const value_type* mp_str {nullptr};
};
// Prior to C++17, a static constexpr class member needs an out-of-class definition.
#ifndef FK_YAML_HAS_CXX_17
template <typename CharT, typename Traits>
constexpr typename basic_str_view<CharT, Traits>::size_type basic_str_view<CharT, Traits>::npos;
#endif // !defined(FK_YAML_HAS_CXX_17)
/// @brief An equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if the two objects are the same, false otherwise.
template <typename CharT, typename Traits>
inline bool operator==(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
// Comparing the lengths first will omit unnecessary value comparison in compare().
return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
}
/// @brief An equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_string object to compare with.
/// @return true if the two objects are the same, false otherwise.
template <typename CharT, typename Traits>
inline bool operator==(basic_str_view<CharT, Traits> lhs, const std::basic_string<CharT, Traits>& rhs) noexcept {
return lhs == basic_str_view<CharT, Traits>(rhs);
}
/// @brief An equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_string object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if the two objects are the same, false otherwise.
template <typename CharT, typename Traits>
inline bool operator==(const std::basic_string<CharT, Traits>& lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return basic_str_view<CharT, Traits>(lhs) == rhs;
}
/// @brief An equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @tparam N The length of the character array.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A character array to compare with.
/// @return true if the two objects are the same, false otherwise.
template <typename CharT, typename Traits, std::size_t N>
inline bool operator==(basic_str_view<CharT, Traits> lhs, const CharT (&rhs)[N]) noexcept {
// assume `rhs` is null terminated
return lhs == basic_str_view<CharT, Traits>(rhs);
}
/// @brief An equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @tparam N The length of the character array.
/// @param rhs A character array for comparison.
/// @param lhs A basic_str_view object to compare with.
/// @return true if the two objects are the same, false otherwise.
template <typename CharT, typename Traits, std::size_t N>
inline bool operator==(const CharT (&lhs)[N], basic_str_view<CharT, Traits> rhs) noexcept {
// assume `lhs` is null terminated
return basic_str_view<CharT, Traits>(lhs) == rhs;
}
/// @brief An not-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if the two objects are different, false otherwise.
template <typename CharT, typename Traits>
inline bool operator!=(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return !(lhs == rhs);
}
/// @brief An not-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_string object to compare with.
/// @return true if the two objects are different, false otherwise.
template <typename CharT, typename Traits>
inline bool operator!=(basic_str_view<CharT, Traits> lhs, const std::basic_string<CharT, Traits>& rhs) noexcept {
return !(lhs == basic_str_view<CharT, Traits>(rhs));
}
/// @brief An not-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_string object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if the two objects are different, false otherwise.
template <typename CharT, typename Traits>
inline bool operator!=(const std::basic_string<CharT, Traits>& lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return !(basic_str_view<CharT, Traits>(lhs) == rhs);
}
/// @brief An not-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @tparam N The length of the character array.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A character array to compare with.
/// @return true if the two objects are different, false otherwise.
template <typename CharT, typename Traits, std::size_t N>
inline bool operator!=(basic_str_view<CharT, Traits> lhs, const CharT (&rhs)[N]) noexcept {
// assume `rhs` is null terminated.
return !(lhs == basic_str_view<CharT, Traits>(rhs, N - 1));
}
/// @brief An not-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @tparam N The length of the character array.
/// @param rhs A character array for comparison.
/// @param lhs A basic_str_view object to compare with.
/// @return true if the two objects are different, false otherwise.
template <typename CharT, typename Traits, std::size_t N>
inline bool operator!=(const CharT (&lhs)[N], basic_str_view<CharT, Traits> rhs) noexcept {
// assume `lhs` is null terminate
return !(basic_str_view<CharT, Traits>(lhs, N - 1) == rhs);
}
/// @brief An less-than operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if `lhs` is less than `rhs`, false otherwise.
template <typename CharT, typename Traits>
inline bool operator<(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return lhs.compare(rhs) < 0;
}
/// @brief An less-than-or-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if `lhs` is less than or equal to `rhs`, false otherwise.
template <typename CharT, typename Traits>
inline bool operator<=(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return lhs.compare(rhs) <= 0;
}
/// @brief An greater-than operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if `lhs` is greater than `rhs`, false otherwise.
template <typename CharT, typename Traits>
inline bool operator>(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return lhs.compare(rhs) > 0;
}
/// @brief An greater-than-or-equal-to operator of the basic_str_view class.
/// @tparam CharT Character type
/// @tparam Traits Character traits type.
/// @param lhs A basic_str_view object for comparison.
/// @param rhs A basic_str_view object to compare with.
/// @return true if `lhs` is greater than or equal to `rhs`, false otherwise.
template <typename CharT, typename Traits>
inline bool operator>=(basic_str_view<CharT, Traits> lhs, basic_str_view<CharT, Traits> rhs) noexcept {
return lhs.compare(rhs) >= 0;
}
/// @brief Insertion operator of the basic_str_view class.
/// @tparam CharT Character type.
/// @tparam Traits Character traits type.
/// @param os An output stream object.
/// @param sv A basic_str_view object.
/// @return Reference to the output stream object `os`.
template <typename CharT, typename Traits>
inline std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& os, basic_str_view<CharT, Traits> sv) {
return os.write(sv.data(), static_cast<std::streamsize>(sv.size()));
}
/// @brief view into `char` sequence.
using str_view = basic_str_view<char>;
#if FK_YAML_HAS_CHAR8_T
/// @brief view into `char8_t` sequence.
using u8str_view = basic_str_view<char8_t>;
#endif
/// @brief view into `char16_t` sequence.
using u16str_view = basic_str_view<char16_t>;
/// @brief view into `char32_t` sequence.
using u32str_view = basic_str_view<char32_t>;
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_STR_VIEW_HPP */
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A position tracker of the target buffer.
class position_tracker {
public:
void set_target_buffer(str_view buffer) noexcept {
m_begin = m_last = buffer.begin();
m_end = buffer.end();
}
/// @brief Update the set of the current position information.
/// @note This function doesn't support cases where cur_pos has moved backward from the last call.
/// @param cur_pos The iterator to the current element of the buffer.
void update_position(const char* p_current) {
const auto diff = static_cast<uint32_t>(p_current - m_last);
if (diff == 0) {
return;
}
m_cur_pos += diff;
const uint32_t prev_lines_read = m_lines_read;
m_lines_read += static_cast<uint32_t>(std::count(m_last, p_current, '\n'));
m_last = p_current;
if (prev_lines_read == m_lines_read) {
m_cur_pos_in_line += diff;
return;
}
uint32_t count = 0;
const char* p_begin = m_begin;
while (--p_current != p_begin) {
if (*p_current == '\n') {
break;
}
count++;
}
m_cur_pos_in_line = count;
}
uint32_t get_cur_pos() const noexcept {
return m_cur_pos;
}
/// @brief Get the current position in the current line.
/// @return uint32_t The current position in the current line.
uint32_t get_cur_pos_in_line() const noexcept {
return m_cur_pos_in_line;
}
/// @brief Get the number of lines which have already been read.
/// @return uint32_t The number of lines which have already been read.
uint32_t get_lines_read() const noexcept {
return m_lines_read;
}
private:
/// The iterator to the beginning element in the target buffer.
const char* m_begin {};
/// The iterator to the past-the-end element in the target buffer.
const char* m_end {};
/// The iterator to the last updated element in the target buffer.
const char* m_last {};
/// The current position from the beginning of an input buffer.
uint32_t m_cur_pos {0};
/// The current position in the current line.
uint32_t m_cur_pos_in_line {0};
/// The number of lines which have already been read.
uint32_t m_lines_read {0};
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_POSITION_TRACKER_HPP */
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/str_view.hpp>
// #include <fkYAML/detail/types/lexical_token_t.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_TYPES_LEXICAL_TOKEN_T_HPP
#define FK_YAML_DETAIL_TYPES_LEXICAL_TOKEN_T_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Definition of lexical token types.
enum class lexical_token_t : std::uint8_t {
END_OF_BUFFER, //!< the end of input buffer.
EXPLICIT_KEY_PREFIX, //!< the character for explicit mapping key prefix `?`.
KEY_SEPARATOR, //!< the key separator `:`
VALUE_SEPARATOR, //!< the value separator `,`
ANCHOR_PREFIX, //!< the character for anchor prefix `&`
ALIAS_PREFIX, //!< the character for alias prefix `*`
YAML_VER_DIRECTIVE, //!< a YAML version directive found. use get_yaml_version() to get a value.
TAG_DIRECTIVE, //!< a TAG directive found. use GetTagInfo() to get the tag information.
TAG_PREFIX, //!< the character for tag prefix `!`
INVALID_DIRECTIVE, //!< an invalid directive found. do not try to get the value.
SEQUENCE_BLOCK_PREFIX, //!< the character for sequence block prefix `- `
SEQUENCE_FLOW_BEGIN, //!< the character for sequence flow begin `[`
SEQUENCE_FLOW_END, //!< the character for sequence flow end `]`
MAPPING_FLOW_BEGIN, //!< the character for mapping begin `{`
MAPPING_FLOW_END, //!< the character for mapping end `}`
PLAIN_SCALAR, //!< plain (unquoted) scalars
SINGLE_QUOTED_SCALAR, //!< single-quoted scalars
DOUBLE_QUOTED_SCALAR, //!< double-quoted scalars
BLOCK_LITERAL_SCALAR, //!< block literal style scalars
BLOCK_FOLDED_SCALAR, //!< block folded style scalars
END_OF_DIRECTIVES, //!< the end of declaration of directives specified by `---`.
END_OF_DOCUMENT, //!< the end of a YAML document specified by `...`.
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_TYPES_LEXICAL_TOKEN_T_HPP */
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Lexical token information
struct lexical_token {
lexical_token() = default;
lexical_token(lexical_token_t t, str_view s) noexcept
: type(t),
str(s) {
}
lexical_token(lexical_token_t t) noexcept
: type(t) {
}
lexical_token(const lexical_token&) = default;
lexical_token& operator=(const lexical_token&) = default;
lexical_token(lexical_token&&) = default;
lexical_token& operator=(lexical_token&&) = default;
~lexical_token() = default;
/// Lexical token type.
lexical_token_t type {lexical_token_t::END_OF_BUFFER};
/// Lexical token contents.
str_view str;
};
/// @brief A class which lexically analyzes YAML formatted inputs.
class lexical_analyzer {
// whether the current context is flow(1) or block(0)
static constexpr uint32_t flow_context_bit = 1u << 0u;
// whether the current document part is directive(1) or content(0)
static constexpr uint32_t document_directive_bit = 1u << 1u;
public:
/// @brief Construct a new lexical_analyzer object.
/// @param input_buffer An input buffer.
explicit lexical_analyzer(str_view input_buffer) noexcept
: m_begin_itr(input_buffer.begin()),
m_cur_itr(input_buffer.begin()),
m_end_itr(input_buffer.end()) {
m_pos_tracker.set_target_buffer(input_buffer);
}
/// @brief Get the next lexical token by scanning the left of the input buffer.
/// @return lexical_token The next lexical token.
lexical_token get_next_token() {
skip_white_spaces_and_newline_codes();
m_token_begin_itr = m_cur_itr;
m_pos_tracker.update_position(m_cur_itr);
m_last_token_begin_pos = m_pos_tracker.get_cur_pos_in_line();
m_last_token_begin_line = m_pos_tracker.get_lines_read();
if (m_cur_itr == m_end_itr) {
return {lexical_token_t::END_OF_BUFFER};
}
switch (*m_cur_itr) {
case '?':
if (++m_cur_itr == m_end_itr) {
return {lexical_token_t::PLAIN_SCALAR, {m_token_begin_itr, 1}};
}
if (*m_cur_itr == ' ') {
return {lexical_token_t::EXPLICIT_KEY_PREFIX};
}
break;
case ':': // key separator
if (++m_cur_itr == m_end_itr) {
return {lexical_token_t::KEY_SEPARATOR};
}
switch (*m_cur_itr) {
case ' ':
case '\t':
case '\n':
return {lexical_token_t::KEY_SEPARATOR};
default:
if ((m_state & flow_context_bit) == 0) {
// in a block context
break;
}
switch (*m_cur_itr) {
case ',':
case '[':
case ']':
case '{':
case '}':
// Flow indicators are not "safe" to be followed in a flow context.
// See https://yaml.org/spec/1.2.2/#733-plain-style for more details.
return {lexical_token_t::KEY_SEPARATOR};
default:
// At least '{' or '[' must precedes this token.
FK_YAML_ASSERT(m_token_begin_itr != m_begin_itr);
// if a key inside a flow mapping is JSON-like (surrounded by indicators, see below), YAML allows
// the following value to be specified adjacent to the ":" mapping value indicator.
// ```yaml
// # the following flow mapping entries are all valid.
// {
// "foo":true,
// 'bar':false, # 'bar' is actually not JSON but allowed in YAML
// # since its surrounded by the single quotes.
// {[1,2,3]:null}:"baz"
// }
// ```
switch (*(m_token_begin_itr - 1)) {
case '\'':
case '\"':
case ']':
case '}':
return {lexical_token_t::KEY_SEPARATOR};
default:
break;
}
break;
}
break;
}
break;
case ',': // value separator
++m_cur_itr;
return {lexical_token_t::VALUE_SEPARATOR};
case '&': // anchor prefix
return {lexical_token_t::ANCHOR_PREFIX, extract_anchor_name()};
case '*': // alias prefix
return {lexical_token_t::ALIAS_PREFIX, extract_anchor_name()};
case '!': // tag prefix
return {lexical_token_t::TAG_PREFIX, extract_tag_name()};
case '#': // comment prefix
scan_comment();
return get_next_token();
case '%': // directive prefix
if (m_state & document_directive_bit) {
return {scan_directive()};
}
// The '%' character can be safely used as the first character in document contents.
// See https://yaml.org/spec/1.2.2/#912-document-markers for more details.
break;
case '-': {
switch (*(m_cur_itr + 1)) {
case ' ':
case '\t':
case '\n':
// Move a cursor to the beginning of the next token.
m_cur_itr += 2;
return {lexical_token_t::SEQUENCE_BLOCK_PREFIX};
default:
break;
}
if (m_pos_tracker.get_cur_pos_in_line() == 0) {
if ((m_end_itr - m_cur_itr) > 2) {
const bool is_dir_end = std::equal(m_token_begin_itr, m_cur_itr + 3, "---");
if (is_dir_end) {
m_cur_itr += 3;
return {lexical_token_t::END_OF_DIRECTIVES};
}
}
}
break;
}
case '[': // sequence flow begin
++m_cur_itr;
return {lexical_token_t::SEQUENCE_FLOW_BEGIN};
case ']': // sequence flow end
++m_cur_itr;
return {lexical_token_t::SEQUENCE_FLOW_END};
case '{': // mapping flow begin
++m_cur_itr;
return {lexical_token_t::MAPPING_FLOW_BEGIN};
case '}': // mapping flow end
++m_cur_itr;
return {lexical_token_t::MAPPING_FLOW_END};
case '@':
emit_error("Any token cannot start with at(@). It is a reserved indicator for YAML.");
case '`':
emit_error("Any token cannot start with grave accent(`). It is a reserved indicator for YAML.");
case '\"':
++m_token_begin_itr;
return {lexical_token_t::DOUBLE_QUOTED_SCALAR, determine_double_quoted_scalar_range()};
case '\'':
++m_token_begin_itr;
return {lexical_token_t::SINGLE_QUOTED_SCALAR, determine_single_quoted_scalar_range()};
case '.': {
if (m_pos_tracker.get_cur_pos_in_line() == 0) {
const auto rem_size = m_end_itr - m_cur_itr;
if FK_YAML_LIKELY (rem_size > 2) {
const bool is_doc_end = std::equal(m_cur_itr, m_cur_itr + 3, "...");
if (is_doc_end) {
if (rem_size > 3) {
switch (*(m_cur_itr + 3)) {
case ' ':
case '\t':
case '\n':
m_cur_itr += 4;
break;
default:
// See https://yaml.org/spec/1.2.2/#912-document-markers for more details.
emit_error("The document end marker \"...\" must not be followed by non-ws char.");
}
}
else {
m_cur_itr += 3;
}
return {lexical_token_t::END_OF_DOCUMENT};
}
}
}
break;
}
case '|':
case '>': {
const str_view sv {m_token_begin_itr, m_end_itr};
const std::size_t header_end_pos = sv.find('\n');
FK_YAML_ASSERT(header_end_pos != str_view::npos);
const uint32_t base_indent = get_current_indent_level(&sv[header_end_pos]);
const lexical_token_t type = *m_token_begin_itr == '|' ? lexical_token_t::BLOCK_LITERAL_SCALAR
: lexical_token_t::BLOCK_FOLDED_SCALAR;
const str_view header_line = sv.substr(1, header_end_pos - 1);
m_block_scalar_header = convert_to_block_scalar_header(header_line);
m_token_begin_itr = sv.begin() + (header_end_pos + 1);
return {
type,
determine_block_scalar_content_range(
base_indent, m_block_scalar_header.indent, m_block_scalar_header.indent)};
}
default:
break;
}
return {lexical_token_t::PLAIN_SCALAR, determine_plain_scalar_range()};
}
/// @brief Get the beginning position of a last token.
/// @return uint32_t The beginning position of a last token.
uint32_t get_last_token_begin_pos() const noexcept {
return m_last_token_begin_pos;
}
/// @brief Get the number of lines already processed.
/// @return uint32_t The number of lines already processed.
uint32_t get_lines_processed() const noexcept {
return m_last_token_begin_line;
}
/// @brief Get the YAML version specification.
/// @return str_view A YAML version specification.
str_view get_yaml_version() const noexcept {
return m_yaml_version;
}
/// @brief Get the YAML tag handle defined in the TAG directive.
/// @return str_view A tag handle.
str_view get_tag_handle() const noexcept {
return m_tag_handle;
}
/// @brief Get the YAML tag prefix defined in the TAG directive.
/// @return str_view A tag prefix.
str_view get_tag_prefix() const noexcept {
return m_tag_prefix;
}
/// @brief Get block scalar header information.
/// @return block_scalar_header Block scalar header information.
block_scalar_header get_block_scalar_header() const noexcept {
return m_block_scalar_header;
}
/// @brief Toggles the context state between flow and block.
/// @param is_flow_context true: flow context, false: block context
void set_context_state(bool is_flow_context) noexcept {
m_state &= ~flow_context_bit;
if (is_flow_context) {
m_state |= flow_context_bit;
}
}
/// @brief Toggles the document state between directive and content.
/// @param is_directive true: directive, false: content
void set_document_state(bool is_directive) noexcept {
m_state &= ~document_directive_bit;
if (is_directive) {
m_state |= document_directive_bit;
}
}
private:
uint32_t get_current_indent_level(const char* p_line_end) {
// get the beginning position of the current line.
std::size_t line_begin_pos = str_view(m_begin_itr, p_line_end - 1).find_last_of('\n');
if (line_begin_pos == str_view::npos) {
line_begin_pos = 0;
}
else {
++line_begin_pos;
}
const char* p_line_begin = m_begin_itr + line_begin_pos;
const char* cur_itr = p_line_begin;
// get the indentation of the current line.
uint32_t indent = 0;
bool indent_found = false;
// 0: none, 1: block seq item, 2: explicit map key, 3: explicit map value
uint32_t context = 0;
while (cur_itr != p_line_end && !indent_found) {
switch (*cur_itr) {
case ' ':
++indent;
++cur_itr;
break;
case '-':
switch (*(cur_itr + 1)) {
case ' ':
case '\t':
indent += 2;
cur_itr += 2;
context = 1;
break;
default:
indent_found = true;
break;
}
break;
case '?':
if (*(cur_itr + 1) == ' ') {
indent += 2;
cur_itr += 2;
context = 2;
break;
}
indent_found = true;
break;
case ':':
switch (*(cur_itr + 1)) {
case ' ':
case '\t':
indent += 2;
cur_itr += 2;
context = 3;
break;
default:
indent_found = true;
break;
}
break;
default:
indent_found = true;
break;
}
}
// If "- ", "? " and/or ": " occur in the first line of this plain scalar content.
if (context > 0) {
// Check if the first line contains the key separator ": ".
// If so, the indent value remains the current one.
// Otherwise, the indent value is changed based on the last ocurrence of the above 3.
// In any case, multiline plain scalar content must be indented more than the indent value.
const str_view line_content_part {p_line_begin + indent, p_line_end};
std::size_t key_sep_pos = line_content_part.find(": ");
if (key_sep_pos == str_view::npos) {
key_sep_pos = line_content_part.find(":\t");
}
if (key_sep_pos == str_view::npos) {
constexpr char targets[] = "-?:";
FK_YAML_ASSERT(context - 1 < sizeof(targets));
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
const char target_char = targets[context - 1];
// Find the position of the last ocuurence of "- ", "? " or ": ".
const str_view line_indent_part {p_line_begin, indent};
const std::size_t block_seq_item_begin_pos = line_indent_part.find_last_of(target_char);
FK_YAML_ASSERT(block_seq_item_begin_pos != str_view::npos);
indent = static_cast<uint32_t>(block_seq_item_begin_pos);
}
}
return indent;
}
/// @brief Skip until a newline code or a null character is found.
void scan_comment() {
FK_YAML_ASSERT(*m_cur_itr == '#');
if FK_YAML_LIKELY (m_cur_itr != m_begin_itr) {
switch (*(m_cur_itr - 1)) {
case ' ':
case '\t':
case '\n':
break;
default:
emit_error("Comment must not begin right after non-break characters");
}
}
skip_until_line_end();
}
/// @brief Scan directives starting with the prefix '%'
/// @note Currently, only %YAML directive is supported. If not, returns invalid or throws an exception.
/// @return lexical_token_t The lexical token type for directives.
lexical_token_t scan_directive() {
FK_YAML_ASSERT(*m_cur_itr == '%');
m_token_begin_itr = ++m_cur_itr;
bool ends_loop = false;
while (!ends_loop && m_cur_itr != m_end_itr) {
switch (*m_cur_itr) {
case ' ':
case '\t':
ends_loop = true;
break;
case '\n':
skip_until_line_end();
return lexical_token_t::INVALID_DIRECTIVE;
default:
++m_cur_itr;
break;
}
}
const str_view dir_name(m_token_begin_itr, m_cur_itr);
if (dir_name == "TAG") {
if FK_YAML_UNLIKELY (!ends_loop) {
emit_error("There must be at least one white space between \"%TAG\" and tag info.");
}
skip_white_spaces();
return scan_tag_directive();
}
if (dir_name == "YAML") {
if FK_YAML_UNLIKELY (!ends_loop) {
emit_error("There must be at least one white space between \"%YAML\" and version.");
}
skip_white_spaces();
return scan_yaml_version_directive();
}
skip_until_line_end();
return lexical_token_t::INVALID_DIRECTIVE;
}
/// @brief Scan a YAML tag directive.
/// @return lexical_token_t The lexical token type for YAML tag directives.
lexical_token_t scan_tag_directive() {
m_token_begin_itr = m_cur_itr;
//
// extract a tag handle
//
if FK_YAML_UNLIKELY (*m_cur_itr != '!') {
emit_error("Tag handle must start with \'!\'.");
}
if FK_YAML_UNLIKELY (++m_cur_itr == m_end_itr) {
emit_error("invalid TAG directive is found.");
}
switch (*m_cur_itr) {
case ' ':
case '\t':
// primary handle (!)
break;
case '!':
if FK_YAML_UNLIKELY (++m_cur_itr == m_end_itr) {
emit_error("invalid TAG directive is found.");
}
if FK_YAML_UNLIKELY (*m_cur_itr != ' ' && *m_cur_itr != '\t') {
emit_error("invalid tag handle is found.");
}
break;
default: {
bool ends_loop = false;
do {
switch (*m_cur_itr) {
case ' ':
case '\t':
emit_error("invalid tag handle is found.");
case '!': {
if (m_cur_itr + 1 == m_end_itr) {
ends_loop = true;
break;
}
const char next = *(m_cur_itr + 1);
if FK_YAML_UNLIKELY (next != ' ' && next != '\t') {
emit_error("invalid tag handle is found.");
}
ends_loop = true;
break;
}
case '-':
break;
default:
if FK_YAML_UNLIKELY (!isalnum(*m_cur_itr)) {
// See https://yaml.org/spec/1.2.2/#rule-c-named-tag-handle for more details.
emit_error("named handle can contain only numbers(0-9), alphabets(A-Z,a-z) and hyphens(-).");
}
break;
}
if FK_YAML_UNLIKELY (++m_cur_itr == m_end_itr) {
emit_error("invalid TAG directive is found.");
}
} while (!ends_loop);
break;
}
}
m_tag_handle = str_view {m_token_begin_itr, m_cur_itr};
skip_white_spaces();
//
// extract a tag prefix.
//
m_token_begin_itr = m_cur_itr;
const char* p_tag_prefix_begin = m_cur_itr;
switch (*m_cur_itr) {
// a tag prefix must not start with flow indicators to avoid ambiguity.
// See https://yaml.org/spec/1.2.2/#rule-ns-global-tag-prefix for more details.
case ',':
case '[':
case ']':
case '{':
case '}':
emit_error("tag prefix must not start with flow indicators (\',\', [], {}).");
default:
break;
}
// extract the rest of a tag prefix.
bool ends_loop = false;
do {
switch (*m_cur_itr) {
case ' ':
case '\t':
case '\n':
ends_loop = true;
break;
default:
break;
}
} while (!ends_loop && ++m_cur_itr != m_end_itr);
const bool is_valid = uri_encoding::validate(p_tag_prefix_begin, m_cur_itr);
if FK_YAML_UNLIKELY (!is_valid) {
emit_error("invalid URI character is found in a tag prefix.");
}
m_tag_prefix = str_view {p_tag_prefix_begin, m_cur_itr};
return lexical_token_t::TAG_DIRECTIVE;
}
/// @brief Scan a YAML version directive.
/// @note Only 1.1 and 1.2 are supported. If not, throws an exception.
/// @return lexical_token_t The lexical token type for YAML version directives.
lexical_token_t scan_yaml_version_directive() {
m_token_begin_itr = m_cur_itr;
bool ends_loop = false;
while (!ends_loop && m_cur_itr != m_end_itr) {
switch (*m_cur_itr) {
case ' ':
case '\t':
case '\n':
ends_loop = true;
break;
default:
++m_cur_itr;
break;
}
}
m_yaml_version = str_view {m_token_begin_itr, m_cur_itr};
if FK_YAML_UNLIKELY (m_yaml_version.compare("1.1") != 0 && m_yaml_version.compare("1.2") != 0) {
emit_error("Only 1.1 and 1.2 can be specified as the YAML version.");
}
return lexical_token_t::YAML_VER_DIRECTIVE;
}
/// @brief Extracts an anchor name from the input.
/// @return The extracted anchor name.
str_view extract_anchor_name() {
FK_YAML_ASSERT(*m_cur_itr == '&' || *m_cur_itr == '*');
m_token_begin_itr = ++m_cur_itr;
bool ends_loop = false;
for (; m_cur_itr != m_end_itr; ++m_cur_itr) {
switch (*m_cur_itr) {
// anchor name must not contain white spaces, newline codes and flow indicators.
// See https://yaml.org/spec/1.2.2/#692-node-anchors for more details.
case ' ':
case '\t':
case '\n':
case '{':
case '}':
case '[':
case ']':
case ',':
ends_loop = true;
break;
default:
break;
}
if (ends_loop) {
break;
}
}
if FK_YAML_UNLIKELY (m_token_begin_itr == m_cur_itr) {
emit_error("anchor name must not be empty.");
}
return {m_token_begin_itr, m_cur_itr};
}
/// @brief Extracts a tag name from the input.
/// @return A tag name.
str_view extract_tag_name() {
FK_YAML_ASSERT(*m_cur_itr == '!');
if (++m_cur_itr == m_end_itr) {
// Just "!" is a non-specific tag.
return {m_token_begin_itr, m_end_itr};
}
bool is_verbatim = false;
bool allows_another_tag_prefix = false;
switch (*m_cur_itr) {
case ' ':
case '\n':
// Just "!" is a non-specific tag.
return {m_token_begin_itr, m_cur_itr};
case '!':
// Secondary tag handles (!!suffix)
break;
case '<':
// Verbatim tags (!<TAG>)
is_verbatim = true;
++m_cur_itr;
break;
default:
// Either local tags (!suffix) or named handles (!tag!suffix)
allows_another_tag_prefix = true;
break;
}
bool is_named_handle = false;
bool ends_loop = false;
do {
if (++m_cur_itr == m_end_itr) {
break;
}
switch (*m_cur_itr) {
// Tag names must not contain spaces or newline codes.
case ' ':
case '\t':
case '\n':
ends_loop = true;
break;
case '!':
if FK_YAML_UNLIKELY (!allows_another_tag_prefix) {
emit_error("invalid tag prefix (!) is found.");
}
is_named_handle = true;
// tag prefix must not appear three times.
allows_another_tag_prefix = false;
break;
default:
break;
}
} while (!ends_loop);
str_view tag_name {m_token_begin_itr, m_cur_itr};
if (is_verbatim) {
const char last = tag_name.back();
if FK_YAML_UNLIKELY (last != '>') {
emit_error("verbatim tag (!<TAG>) must be ended with \'>\'.");
}
// only the `TAG` part of the `!<TAG>` for URI validation.
const str_view tag_body = tag_name.substr(2, tag_name.size() - 3);
if FK_YAML_UNLIKELY (tag_body.empty()) {
emit_error("verbatim tag(!<TAG>) must not be empty.");
}
const bool is_valid_uri = uri_encoding::validate(tag_body.begin(), tag_body.end());
if FK_YAML_UNLIKELY (!is_valid_uri) {
emit_error("invalid URI character is found in a verbatim tag.");
}
return tag_name;
}
if (is_named_handle) {
const char last = tag_name.back();
if FK_YAML_UNLIKELY (last == '!') {
// Tag shorthand must be followed by a non-empty suffix.
// See the "Tag Shorthands" section in https://yaml.org/spec/1.2.2/#691-node-tags.
emit_error("named handle has no suffix.");
}
}
// get the position of last tag prefix character (!) to extract body of tag shorthands.
// tag shorthand is either primary(!tag), secondary(!!tag) or named(!handle!tag).
const std::size_t last_tag_prefix_pos = tag_name.find_last_of('!');
FK_YAML_ASSERT(last_tag_prefix_pos != str_view::npos);
const str_view tag_uri = tag_name.substr(last_tag_prefix_pos + 1);
const bool is_valid_uri = uri_encoding::validate(tag_uri.begin(), tag_uri.end());
if FK_YAML_UNLIKELY (!is_valid_uri) {
emit_error("Invalid URI character is found in a named tag handle.");
}
// Tag shorthands cannot contain flow indicators({}[],).
// See the "Tag Shorthands" section in https://yaml.org/spec/1.2.2/#691-node-tags.
const std::size_t invalid_char_pos = tag_uri.find_first_of("{}[],");
if (invalid_char_pos != str_view::npos) {
emit_error("Tag shorthand cannot contain flow indicators({}[],).");
}
return tag_name;
}
/// @brief Determines the range of single quoted scalar by scanning remaining input buffer contents.
/// @return A single quoted scalar.
str_view determine_single_quoted_scalar_range() {
const str_view sv {m_token_begin_itr, m_end_itr};
std::size_t pos = sv.find('\'');
while (pos != str_view::npos) {
FK_YAML_ASSERT(pos < sv.size());
if FK_YAML_LIKELY (pos == sv.size() - 1 || sv[pos + 1] != '\'') {
// closing single quote is found.
m_cur_itr = m_token_begin_itr + (pos + 1);
str_view single_quoted_scalar {m_token_begin_itr, pos};
check_scalar_content(single_quoted_scalar);
return single_quoted_scalar;
}
// If single quotation marks are repeated twice in a single quoted scalar, they are considered as an
// escaped single quotation mark. Skip the second one which would otherwise be detected as a closing
// single quotation mark in the next loop.
pos = sv.find('\'', pos + 2);
}
m_cur_itr = m_end_itr; // update for error information
emit_error("Invalid end of input buffer in a single-quoted scalar token.");
}
/// @brief Determines the range of double quoted scalar by scanning remaining input buffer contents.
/// @return A double quoted scalar.
str_view determine_double_quoted_scalar_range() {
const str_view sv {m_token_begin_itr, m_end_itr};
std::size_t pos = sv.find('\"');
while (pos != str_view::npos) {
FK_YAML_ASSERT(pos < sv.size());
bool is_closed = true;
if FK_YAML_LIKELY (pos > 0) {
// Double quotation marks can be escaped by a preceding backslash and the number of backslashes matters
// to determine if the found double quotation mark is escaped since the backslash itself can also be
// escaped:
// * odd number of backslashes -> double quotation mark IS escaped (e.g., "\\\"")
// * even number of backslashes -> double quotation mark IS NOT escaped (e.g., "\\"")
uint32_t backslash_counts = 0;
const char* p = m_token_begin_itr + (pos - 1);
do {
if (*p-- != '\\') {
break;
}
++backslash_counts;
} while (p != m_token_begin_itr);
is_closed = ((backslash_counts & 1u) == 0); // true: even, false: odd
}
if (is_closed) {
// closing double quote is found.
m_cur_itr = m_token_begin_itr + (pos + 1);
str_view double_quoted_scalar {m_token_begin_itr, pos};
check_scalar_content(double_quoted_scalar);
return double_quoted_scalar;
}
pos = sv.find('\"', pos + 1);
}
m_cur_itr = m_end_itr; // update for error information
emit_error("Invalid end of input buffer in a double-quoted scalar token.");
}
/// @brief Determines the range of plain scalar by scanning remaining input buffer contents.
/// @return A plain scalar.
str_view determine_plain_scalar_range() {
const str_view sv {m_token_begin_itr, m_end_itr};
// flow indicators are checked only within a flow context.
const str_view filter = (m_state & flow_context_bit) ? "\t\n :{}[]," : "\t\n :";
std::size_t pos = sv.find_first_of(filter);
if FK_YAML_UNLIKELY (pos == str_view::npos) {
check_scalar_content(sv);
m_cur_itr = m_end_itr;
return sv;
}
bool ends_loop = false;
uint32_t indent = std::numeric_limits<uint32_t>::max();
do {
FK_YAML_ASSERT(pos < sv.size());
switch (sv[pos]) {
case '\n': {
if (indent == std::numeric_limits<uint32_t>::max()) {
indent = get_current_indent_level(&sv[pos]);
}
constexpr str_view space_filter {" \t\n"};
const std::size_t non_space_pos = sv.find_first_not_of(space_filter, pos);
const std::size_t last_newline_pos = sv.find_last_of('\n', non_space_pos);
FK_YAML_ASSERT(last_newline_pos != str_view::npos);
if (non_space_pos == str_view::npos || non_space_pos - last_newline_pos - 1 <= indent) {
ends_loop = true;
break;
}
pos = non_space_pos;
break;
}
case ' ':
case '\t':
if FK_YAML_UNLIKELY (pos == sv.size() - 1) {
// trim trailing space.
ends_loop = true;
break;
}
// Allow a space in a plain scalar only if the space is surrounded by non-space characters, but not
// followed by the comment prefix " #".
// Also, flow indicators are not allowed to be followed after a space in a flow context.
// See https://yaml.org/spec/1.2.2/#733-plain-style for more details.
switch (sv[pos + 1]) {
case ' ':
case '\t':
case '\n':
case '#':
ends_loop = true;
break;
case ':':
// " :" is permitted in a plain style string token, but not when followed by a space.
ends_loop = (pos < sv.size() - 2) && (sv[pos + 2] == ' ');
break;
case '{':
case '}':
case '[':
case ']':
case ',':
ends_loop = (m_state & flow_context_bit);
break;
default:
break;
}
break;
case ':':
if FK_YAML_LIKELY (pos + 1 < sv.size()) {
switch (sv[pos + 1]) {
case ' ':
case '\t':
case '\n':
ends_loop = true;
break;
default:
break;
}
}
break;
case '{':
case '}':
case '[':
case ']':
case ',':
// This check is enabled only in a flow context.
ends_loop = true;
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
if (ends_loop) {
break;
}
pos = sv.find_first_of(filter, pos + 1);
} while (pos != str_view::npos);
str_view plain_scalar = sv.substr(0, pos);
check_scalar_content(plain_scalar);
m_cur_itr = plain_scalar.end();
return plain_scalar;
}
/// @brief Scan a block style string token either in the literal or folded style.
/// @param base_indent The base indent level of the block scalar.
/// @param indicated_indent The indicated indent level in the block scalar header. 0 means it's not indicated.
/// @param token Storage for the scanned block scalar range.
/// @return The content indentation level of the block scalar.
str_view determine_block_scalar_content_range(
uint32_t base_indent, uint32_t indicated_indent, uint32_t& content_indent) {
const str_view sv {m_token_begin_itr, m_end_itr};
const std::size_t remain_input_len = sv.size();
// Handle leading all-space lines.
uint32_t cur_indent = 0;
uint32_t max_leading_indent = 0;
const char* cur_itr = m_token_begin_itr;
bool stop_increment = false;
while (cur_itr != m_end_itr) {
switch (*cur_itr++) {
case ' ':
if FK_YAML_LIKELY (!stop_increment) {
++cur_indent;
}
continue;
case '\t':
// Tabs are not counted as an indent character but still part of an empty line.
// See https://yaml.org/spec/1.2.2/#rule-s-indent and https://yaml.org/spec/1.2.2/#64-empty-lines.
stop_increment = true;
continue;
case '\n':
max_leading_indent = std::max(cur_indent, max_leading_indent);
cur_indent = 0;
stop_increment = false;
continue;
default:
break;
}
break;
}
// all the block scalar contents are empty lines, and no subsequent token exists.
if FK_YAML_UNLIKELY (cur_itr == m_end_itr) {
// Without the following iterator update, lexer cannot reach the end of input buffer and causes infinite
// loops from the next loop. (https://github.com/fktn-k/fkYAML/pull/410)
m_cur_itr = m_end_itr;
// If there's no non-empty line, the content indentation level is equal to the number of spaces on the
// longest line. https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator
content_indent =
indicated_indent == 0 ? std::max(cur_indent, max_leading_indent) : base_indent + indicated_indent;
return sv;
}
// Any leading empty line must not contain more spaces than the first non-empty line.
if FK_YAML_UNLIKELY (cur_indent < max_leading_indent) {
emit_error("Any leading empty line must not be more indented than the first non-empty line.");
}
if (indicated_indent == 0) {
FK_YAML_ASSERT(base_indent < cur_indent);
indicated_indent = cur_indent - base_indent;
}
else if FK_YAML_UNLIKELY (cur_indent < base_indent + indicated_indent) {
emit_error("The first non-empty line in the block scalar is less indented.");
}
std::size_t last_newline_pos = sv.find('\n', cur_itr - m_token_begin_itr + 1);
if (last_newline_pos == str_view::npos) {
last_newline_pos = remain_input_len;
}
content_indent = base_indent + indicated_indent;
while (last_newline_pos < remain_input_len) {
std::size_t cur_line_end_pos = sv.find('\n', last_newline_pos + 1);
if (cur_line_end_pos == str_view::npos) {
cur_line_end_pos = remain_input_len;
}
const std::size_t cur_line_content_begin_pos = sv.find_first_not_of(' ', last_newline_pos + 1);
if (cur_line_content_begin_pos == str_view::npos) {
last_newline_pos = cur_line_end_pos;
continue;
}
FK_YAML_ASSERT(last_newline_pos < cur_line_content_begin_pos);
cur_indent = static_cast<uint32_t>(cur_line_content_begin_pos - last_newline_pos - 1);
if (cur_indent < content_indent && sv[cur_line_content_begin_pos] != '\n') {
if FK_YAML_UNLIKELY (cur_indent > base_indent) {
// This path assumes an input like the following:
// ```yaml
// foo: |
// text
// invalid # this line is less indented than the content indent level (2)
// # but more indented than the base indent level (0)
// ```
// In such cases, the less indented line cannot be the start of the next token.
emit_error("A content line of the block scalar is less indented.");
}
// Interpret less indented non-space characters as the start of the next token.
break;
}
last_newline_pos = cur_line_end_pos;
}
// include last newline character if not all characters have been consumed yet.
if (last_newline_pos < remain_input_len) {
++last_newline_pos;
}
m_cur_itr = m_token_begin_itr + last_newline_pos;
return sv.substr(0, last_newline_pos);
}
/// @brief Checks if the given scalar contains no unescaped control characters.
/// @param scalar Scalar contents.
void check_scalar_content(const str_view& scalar) const {
const char* p_current = scalar.begin();
const char* p_end = scalar.end();
while (p_current != p_end) {
const uint32_t num_bytes = utf8::get_num_bytes(static_cast<uint8_t>(*p_current));
if (num_bytes > 1) {
// Multibyte characters are already checked in the input_adapter module.
p_current += num_bytes;
continue;
}
switch (*p_current++) {
// 0x00(NULL) has already been handled above.
case 0x01:
emit_error("Control character U+0001 (SOH) must be escaped to \\u0001.");
case 0x02:
emit_error("Control character U+0002 (STX) must be escaped to \\u0002.");
case 0x03:
emit_error("Control character U+0003 (ETX) must be escaped to \\u0003.");
case 0x04:
emit_error("Control character U+0004 (EOT) must be escaped to \\u0004.");
case 0x05:
emit_error("Control character U+0005 (ENQ) must be escaped to \\u0005.");
case 0x06:
emit_error("Control character U+0006 (ACK) must be escaped to \\u0006.");
case 0x07:
emit_error("Control character U+0007 (BEL) must be escaped to \\a or \\u0007.");
case 0x08:
emit_error("Control character U+0008 (BS) must be escaped to \\b or \\u0008.");
case 0x09: // HT
// horizontal tabs (\t) are safe to use without escaping.
break;
// 0x0A(LF) has already been handled above.
case 0x0B:
emit_error("Control character U+000B (VT) must be escaped to \\v or \\u000B.");
case 0x0C:
emit_error("Control character U+000C (FF) must be escaped to \\f or \\u000C.");
// 0x0D(CR) has already been handled above.
case 0x0E:
emit_error("Control character U+000E (SO) must be escaped to \\u000E.");
case 0x0F:
emit_error("Control character U+000F (SI) must be escaped to \\u000F.");
case 0x10:
emit_error("Control character U+0010 (DLE) must be escaped to \\u0010.");
case 0x11:
emit_error("Control character U+0011 (DC1) must be escaped to \\u0011.");
case 0x12:
emit_error("Control character U+0012 (DC2) must be escaped to \\u0012.");
case 0x13:
emit_error("Control character U+0013 (DC3) must be escaped to \\u0013.");
case 0x14:
emit_error("Control character U+0014 (DC4) must be escaped to \\u0014.");
case 0x15:
emit_error("Control character U+0015 (NAK) must be escaped to \\u0015.");
case 0x16:
emit_error("Control character U+0016 (SYN) must be escaped to \\u0016.");
case 0x17:
emit_error("Control character U+0017 (ETB) must be escaped to \\u0017.");
case 0x18:
emit_error("Control character U+0018 (CAN) must be escaped to \\u0018.");
case 0x19:
emit_error("Control character U+0019 (EM) must be escaped to \\u0019.");
case 0x1A:
emit_error("Control character U+001A (SUB) must be escaped to \\u001A.");
case 0x1B:
emit_error("Control character U+001B (ESC) must be escaped to \\e or \\u001B.");
case 0x1C:
emit_error("Control character U+001C (FS) must be escaped to \\u001C.");
case 0x1D:
emit_error("Control character U+001D (GS) must be escaped to \\u001D.");
case 0x1E:
emit_error("Control character U+001E (RS) must be escaped to \\u001E.");
case 0x1F:
emit_error("Control character U+001F (US) must be escaped to \\u001F.");
default:
break;
}
}
}
/// @brief Gets the metadata of a following block style string scalar.
/// @param chomp_type A variable to store the retrieved chomping style type.
/// @param indent A variable to store the retrieved indent size.
/// @return Block scalar header information converted from the header line.
block_scalar_header convert_to_block_scalar_header(str_view line) {
constexpr str_view comment_prefix {" #"};
const std::size_t comment_begin_pos = line.find(comment_prefix);
if (comment_begin_pos != str_view::npos) {
line = line.substr(0, comment_begin_pos);
}
if (line.empty()) {
return {};
}
block_scalar_header header {};
for (const char c : line) {
switch (c) {
case '-':
if FK_YAML_UNLIKELY (header.chomp != chomping_indicator_t::CLIP) {
emit_error("Too many block chomping indicators specified.");
}
header.chomp = chomping_indicator_t::STRIP;
break;
case '+':
if FK_YAML_UNLIKELY (header.chomp != chomping_indicator_t::CLIP) {
emit_error("Too many block chomping indicators specified.");
}
header.chomp = chomping_indicator_t::KEEP;
break;
case '0':
emit_error("An indentation level for a block scalar cannot be 0.");
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if FK_YAML_UNLIKELY (header.indent > 0) {
emit_error("Invalid indentation level for a block scalar. It must be between 1 and 9.");
}
header.indent = static_cast<uint32_t>(c - '0');
break;
case ' ':
case '\t':
break;
default:
emit_error("Invalid character found in a block scalar header.");
}
}
return header;
}
/// @brief Skip white spaces (half-width spaces and tabs) from the current position.
void skip_white_spaces() {
m_cur_itr = std::find_if_not(m_cur_itr, m_end_itr, [](char c) { return (c == ' ' || c == '\t'); });
}
/// @brief Skip white spaces and newline codes (CR/LF) from the current position.
void skip_white_spaces_and_newline_codes() {
if (m_cur_itr != m_end_itr) {
m_cur_itr = std::find_if_not(m_cur_itr, m_end_itr, [](char c) {
switch (c) {
case ' ':
case '\t':
case '\n':
return true;
default:
return false;
}
});
}
}
/// @brief Skip the rest in the current line.
void skip_until_line_end() {
while (m_cur_itr != m_end_itr) {
switch (*m_cur_itr) {
case '\n':
++m_cur_itr;
return;
default:
++m_cur_itr;
break;
}
}
}
/// @brief Emits an error with the given message.
/// @param msg A message for the resulting error.
[[noreturn]] void emit_error(const char* msg) const {
m_pos_tracker.update_position(m_cur_itr);
throw fkyaml::parse_error(msg, m_pos_tracker.get_lines_read(), m_pos_tracker.get_cur_pos_in_line());
}
private:
/// The iterator to the first element in the input buffer.
const char* m_begin_itr {};
/// The iterator to the current character in the input buffer.
const char* m_cur_itr {};
/// The iterator to the beginning of the current token.
const char* m_token_begin_itr {};
/// The iterator to the past-the-end element in the input buffer.
const char* m_end_itr {};
/// The current position tracker of the input buffer.
mutable position_tracker m_pos_tracker {};
/// The last yaml version.
str_view m_yaml_version;
/// The last tag handle.
str_view m_tag_handle;
/// The last tag prefix.
str_view m_tag_prefix;
/// The last block scalar header.
block_scalar_header m_block_scalar_header {};
/// The beginning position of the last lexical token. (zero origin)
uint32_t m_last_token_begin_pos {0};
/// The beginning line of the last lexical token. (zero origin)
uint32_t m_last_token_begin_line {0};
/// The current depth of flow context.
uint32_t m_state {0};
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_LEXICAL_ANALYZER_HPP */
// #include <fkYAML/detail/input/scalar_parser.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_PARSER_HPP
#define FK_YAML_DETAIL_INPUT_SCALAR_PARSER_HPP
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/detail/conversions/scalar_conv.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
// **NOTE FOR LIBRARY DEVELOPERS**:
// Implementations in this header file are intentionally optimized for conversions between YAML scalars and native C++
// types. So, some implementations don't follow the conversions in the standard C++ functions. For example, octals must
// begin with "0o" (not "0"), which is specified in the YAML spec 1.2.
#ifndef FK_YAML_CONVERSIONS_SCALAR_CONV_HPP
#define FK_YAML_CONVERSIONS_SCALAR_CONV_HPP
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
#if FK_YAML_HAS_TO_CHARS
// Prefer std::to_chars() and std::from_chars() functions if available.
#include <charconv>
#else
// Fallback to legacy string conversion functions otherwise.
#include <string> // std::stof(), std::stod(), std::stold()
#endif
FK_YAML_DETAIL_NAMESPACE_BEGIN
//////////////////////////
// conv_limits_base //
//////////////////////////
/// @brief A structure which provides limits for conversions between scalars and integers.
/// @note This structure contains common limits in both signed and unsigned integers.
/// @tparam NumBytes The number of bytes for the integer type.
template <std::size_t NumBytes>
struct conv_limits_base {};
/// @brief The specialization of conv_limits_base for 1 byte integers, e.g., int8_t, uint8_t.
template <>
struct conv_limits_base<1u> {
/// max characters for octals (0o377) without the prefix part.
static constexpr std::size_t max_chars_oct = 3;
/// max characters for hexadecimals (0xFF) without the prefix part.
static constexpr std::size_t max_chars_hex = 2;
/// @brief Check if the given octals are safely converted into 1 byte integer.
/// @param octs The pointer to octal characters
/// @param len The length of octal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_octs_safe(const char* octs, std::size_t len) noexcept {
return (len < max_chars_oct) || (len == max_chars_oct && octs[0] <= '3');
}
/// @brief Check if the given hexadecimals are safely converted into 1 byte integer.
/// @param octs The pointer to hexadecimal characters
/// @param len The length of hexadecimal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_hexs_safe(const char* /*unused*/, std::size_t len) noexcept {
return len <= max_chars_hex;
}
};
/// @brief The specialization of conv_limits_base for 2 byte integers, e.g., int16_t, uint16_t.
template <>
struct conv_limits_base<2u> {
/// max characters for octals (0o177777) without the prefix part.
static constexpr std::size_t max_chars_oct = 6;
/// max characters for hexadecimals (0xFFFF) without the prefix part.
static constexpr std::size_t max_chars_hex = 4;
/// @brief Check if the given octals are safely converted into 2 byte integer.
/// @param octs The pointer to octal characters
/// @param len The length of octal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_octs_safe(const char* octs, std::size_t len) noexcept {
return (len < max_chars_oct) || (len == max_chars_oct && octs[0] <= '1');
}
/// @brief Check if the given hexadecimals are safely converted into 2 byte integer.
/// @param octs The pointer to hexadecimal characters
/// @param len The length of hexadecimal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_hexs_safe(const char* /*unused*/, std::size_t len) noexcept {
return len <= max_chars_hex;
}
};
/// @brief The specialization of conv_limits_base for 4 byte integers, e.g., int32_t, uint32_t.
template <>
struct conv_limits_base<4u> {
/// max characters for octals (0o37777777777) without the prefix part.
static constexpr std::size_t max_chars_oct = 11;
/// max characters for hexadecimals (0xFFFFFFFF) without the prefix part.
static constexpr std::size_t max_chars_hex = 8;
/// @brief Check if the given octals are safely converted into 4 byte integer.
/// @param octs The pointer to octal characters
/// @param len The length of octal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_octs_safe(const char* octs, std::size_t len) noexcept {
return (len < max_chars_oct) || (len == max_chars_oct && octs[0] <= '3');
}
/// @brief Check if the given hexadecimals are safely converted into 4 byte integer.
/// @param octs The pointer to hexadecimal characters
/// @param len The length of hexadecimal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_hexs_safe(const char* /*unused*/, std::size_t len) noexcept {
return len <= max_chars_hex;
}
};
/// @brief The specialization of conv_limits_base for 8 byte integers, e.g., int64_t, uint64_t.
template <>
struct conv_limits_base<8u> {
/// max characters for octals (0o1777777777777777777777) without the prefix part.
static constexpr std::size_t max_chars_oct = 22;
/// max characters for hexadecimals (0xFFFFFFFFFFFFFFFF) without the prefix part.
static constexpr std::size_t max_chars_hex = 16;
/// @brief Check if the given octals are safely converted into 8 byte integer.
/// @param octs The pointer to octal characters
/// @param len The length of octal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_octs_safe(const char* octs, std::size_t len) noexcept {
return (len < max_chars_oct) || (len == max_chars_oct && octs[0] <= '1');
}
/// @brief Check if the given hexadecimals are safely converted into 8 byte integer.
/// @param octs The pointer to hexadecimal characters
/// @param len The length of hexadecimal characters
/// @return true is safely convertible, false otherwise.
static bool check_if_hexs_safe(const char* /*unused*/, std::size_t len) noexcept {
return len <= max_chars_hex;
}
};
/////////////////////
// conv_limits //
/////////////////////
/// @brief A structure which provides limits for conversions between scalars and integers.
/// @note This structure contains limits which differs based on signedness.
/// @tparam NumBytes The number of bytes for the integer type.
/// @tparam IsSigned Whether an integer is signed or unsigned
template <std::size_t NumBytes, bool IsSigned>
struct conv_limits {};
/// @brief The specialization of conv_limits for 1 byte signed integers, e.g., int8_t.
template <>
struct conv_limits<1u, true> : conv_limits_base<1u> {
/// with or without sign.
static constexpr bool is_signed = true;
/// max characters for decimals (-128..127) without sign.
static constexpr std::size_t max_chars_dec = 3;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
// Making this function a static constexpr variable, a link error happens.
// Although the issue has been fixed since C++17, this workaround is necessary to let this functionality work
// with C++11 (the library's default C++ standard version).
// The same thing is applied to similar functions in the other specializations.
static constexpr char max_value_chars[] = "127";
return &max_value_chars[0];
}
/// string representation of min decimal value without sign.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "128";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 1 byte unsigned integers, e.g., uint8_t.
template <>
struct conv_limits<1u, false> : conv_limits_base<1u> {
/// with or without sign.
static constexpr bool is_signed = false;
/// max characters for decimals (0..255) without sign.
static constexpr std::size_t max_chars_dec = 3;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "255";
return &max_value_chars[0];
}
/// string representation of min decimal value.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "0";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 2 byte signed integers, e.g., int16_t.
template <>
struct conv_limits<2u, true> : conv_limits_base<2u> {
/// with or without sign.
static constexpr bool is_signed = true;
/// max characters for decimals (-32768..32767) without sign.
static constexpr std::size_t max_chars_dec = 5;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "32767";
return &max_value_chars[0];
}
/// string representation of min decimal value without sign.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "32768";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 2 byte unsigned integers, e.g., uint16_t.
template <>
struct conv_limits<2u, false> : conv_limits_base<2u> {
/// with or without sign.
static constexpr bool is_signed = false;
/// max characters for decimals (0..65535) without sign.
static constexpr std::size_t max_chars_dec = 5;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "65535";
return &max_value_chars[0];
}
/// string representation of min decimal value.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "0";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 4 byte signed integers, e.g., int32_t.
template <>
struct conv_limits<4u, true> : conv_limits_base<4u> {
/// with or without sign.
static constexpr bool is_signed = true;
/// max characters for decimals (-2147483648..2147483647) without sign.
static constexpr std::size_t max_chars_dec = 10;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "2147483647";
return &max_value_chars[0];
}
/// string representation of min decimal value without sign.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "2147483648";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 4 byte unsigned integers, e.g., uint32_t.
template <>
struct conv_limits<4u, false> : conv_limits_base<4u> {
/// with or without sign.
static constexpr bool is_signed = false;
/// max characters for decimals (0..4294967295) without sign.
static constexpr std::size_t max_chars_dec = 10;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "4294967295";
return &max_value_chars[0];
}
/// string representation of min decimal value.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "0";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 8 byte signed integers, e.g., int64_t.
template <>
struct conv_limits<8u, true> : conv_limits_base<8u> {
/// with or without sign.
static constexpr bool is_signed = true;
/// max characters for decimals (-9223372036854775808..9223372036854775807) without sign.
static constexpr std::size_t max_chars_dec = 19;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "9223372036854775807";
return &max_value_chars[0];
}
/// string representation of min decimal value without sign.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "9223372036854775808";
return &min_value_chars[0];
}
};
/// @brief The specialization of conv_limits for 8 byte unsigned integers, e.g., uint64_t.
template <>
struct conv_limits<8u, false> : conv_limits_base<8u> {
/// with or without sign.
static constexpr bool is_signed = false;
/// max characters for decimals (0..18446744073709551615) without sign.
static constexpr std::size_t max_chars_dec = 20;
/// string representation of max decimal value.
static const char* max_value_chars_dec() noexcept {
static constexpr char max_value_chars[] = "18446744073709551615";
return &max_value_chars[0];
}
/// string representation of min decimal value.
static const char* min_value_chars_dec() noexcept {
static constexpr char min_value_chars[] = "0";
return &min_value_chars[0];
}
};
//////////////////////////
// scalar <--> null //
//////////////////////////
/// @brief Converts a scalar into a null value
/// @tparam CharItr Type of char iterators. Its value type must be `char` (maybe cv-qualified).
/// @param begin The iterator to the first element of the scalar.
/// @param end The iterator to the past-the-end element of the scalar.
/// @param /*unused*/ The null value holder (unused since it can only have `nullptr`)
/// @return true if the conversion completes successfully, false otherwise.
template <typename CharItr>
inline bool aton(CharItr begin, CharItr end, std::nullptr_t& /*unused*/) noexcept {
static_assert(is_iterator_of<CharItr, char>::value, "aton() accepts iterators for char type");
if FK_YAML_UNLIKELY (begin == end) {
return false;
}
const auto len = static_cast<uint32_t>(std::distance(begin, end));
// This path is the most probable case, so check it first.
if FK_YAML_LIKELY (len == 4) {
const char* p_begin = &*begin;
return (std::strncmp(p_begin, "null", 4) == 0) || (std::strncmp(p_begin, "Null", 4) == 0) ||
(std::strncmp(p_begin, "NULL", 4) == 0);
}
if (len == 1) {
return *begin == '~';
}
return false;
}
/////////////////////////////
// scalar <--> boolean //
/////////////////////////////
/// @brief Converts a scalar into a boolean value
/// @tparam CharItr The type of char iterators. Its value type must be `char` (maybe cv-qualified).
/// @tparam BoolType The output boolean type.
/// @param begin The iterator to the first element of the scalar.
/// @param end The iterator to the past-the-end element of the scalar.
/// @param boolean The boolean value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename CharItr, typename BoolType>
inline bool atob(CharItr begin, CharItr end, BoolType& boolean) noexcept {
static_assert(is_iterator_of<CharItr, char>::value, "atob() accepts iterators for char type");
if FK_YAML_UNLIKELY (begin == end) {
return false;
}
const auto len = static_cast<uint32_t>(std::distance(begin, end));
const char* p_begin = &*begin;
if (len == 4) {
const bool is_true = (std::strncmp(p_begin, "true", 4) == 0) || (std::strncmp(p_begin, "True", 4) == 0) ||
(std::strncmp(p_begin, "TRUE", 4) == 0);
if FK_YAML_LIKELY (is_true) {
boolean = static_cast<BoolType>(true);
}
return is_true;
}
if (len == 5) {
const bool is_false = (std::strncmp(p_begin, "false", 5) == 0) || (std::strncmp(p_begin, "False", 5) == 0) ||
(std::strncmp(p_begin, "FALSE", 5) == 0);
if FK_YAML_LIKELY (is_false) {
boolean = static_cast<BoolType>(false);
}
return is_false;
}
return false;
}
/////////////////////////////
// scalar <--> integer //
/////////////////////////////
//
// scalar --> decimals
//
/// @brief Converts a scalar into decimals. This is common implementation for both signed/unsigned integer types.
/// @warning
/// This function does NOT care about overflows if IntType is unsigned. The source string value must be validated
/// beforehand by calling either atoi_dec_pos() or atoi_dec_neg() functions.
/// Furthermore, `p_begin` and `p_end` must NOT be null. Validate them before calling this function.
/// @tparam IntType The output integer type. It can be either signed or unsigned.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename IntType>
inline bool atoi_dec_unchecked(const char* p_begin, const char* p_end, IntType& i) noexcept {
static_assert(
is_non_bool_integral<IntType>::value,
"atoi_dec_unchecked() accepts non-boolean integral types as an output type");
i = 0;
do {
const char c = *p_begin;
if FK_YAML_UNLIKELY (c < '0' || '9' < c) {
return false;
}
// Overflow is intentional when the IntType is signed.
i = i * static_cast<IntType>(10) + static_cast<IntType>(c - '0');
} while (++p_begin != p_end);
return true;
}
/// @brief Converts a scalar into positive decimals. This function executes bounds check to avoid overflow.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @tparam IntType The output integer type. It can be either signed or unsigned.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename IntType>
inline bool atoi_dec_pos(const char* p_begin, const char* p_end, IntType& i) noexcept {
static_assert(
is_non_bool_integral<IntType>::value, "atoi_dec_pos() accepts non-boolean integral types as an output type");
if FK_YAML_UNLIKELY (p_begin == p_end) {
return false;
}
using conv_limits_type = conv_limits<sizeof(IntType), std::is_signed<IntType>::value>;
const auto len = static_cast<std::size_t>(p_end - p_begin);
if FK_YAML_UNLIKELY (len > conv_limits_type::max_chars_dec) {
// Overflow will happen.
return false;
}
if (len == conv_limits_type::max_chars_dec) {
const char* p_max_value_chars_dec = conv_limits_type::max_value_chars_dec();
for (std::size_t idx = 0; idx < conv_limits_type::max_chars_dec; idx++) {
if (p_begin[idx] < p_max_value_chars_dec[idx]) {
// No need to check the lower digits. Overflow will no longer happen.
break;
}
if FK_YAML_UNLIKELY (p_begin[idx] > p_max_value_chars_dec[idx]) {
// Overflow will happen.
return false;
}
}
}
return atoi_dec_unchecked(p_begin, p_end, i);
}
/// @brief Converts a scalar into negative decimals. This function executes bounds check to avoid underflow.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @tparam IntType The output integer type. It must be signed.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename IntType>
inline bool atoi_dec_neg(const char* p_begin, const char* p_end, IntType& i) noexcept {
static_assert(
is_non_bool_integral<IntType>::value, "atoi_dec_neg() accepts non-boolean integral types as an output type");
if FK_YAML_UNLIKELY (p_begin == p_end) {
return false;
}
using conv_limits_type = conv_limits<sizeof(IntType), std::is_signed<IntType>::value>;
const auto len = static_cast<std::size_t>(p_end - p_begin);
if FK_YAML_UNLIKELY (len > conv_limits_type::max_chars_dec) {
// Underflow will happen.
return false;
}
if (len == conv_limits_type::max_chars_dec) {
const char* p_min_value_chars_dec = conv_limits_type::min_value_chars_dec();
for (std::size_t idx = 0; idx < conv_limits_type::max_chars_dec; idx++) {
if (p_begin[idx] < p_min_value_chars_dec[idx]) {
// No need to check the lower digits. Underflow will no longer happen.
break;
}
if FK_YAML_UNLIKELY (p_begin[idx] > p_min_value_chars_dec[idx]) {
// Underflow will happen.
return false;
}
}
}
return atoi_dec_unchecked(p_begin, p_end, i);
}
//
// scalar --> octals
//
/// @brief Converts a scalar into octals. This function executes bounds check to avoid overflow.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @tparam IntType The output integer type. It can be either signed or unsigned.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename IntType>
inline bool atoi_oct(const char* p_begin, const char* p_end, IntType& i) noexcept {
static_assert(
is_non_bool_integral<IntType>::value, "atoi_oct() accepts non-boolean integral types as an output type");
if FK_YAML_UNLIKELY (p_begin == p_end) {
return false;
}
using conv_limits_type = conv_limits<sizeof(IntType), std::is_signed<IntType>::value>;
const auto len = static_cast<std::size_t>(p_end - p_begin);
if FK_YAML_UNLIKELY (!conv_limits_type::check_if_octs_safe(p_begin, len)) {
return false;
}
i = 0;
do {
const char c = *p_begin;
if FK_YAML_UNLIKELY (c < '0' || '7' < c) {
return false;
}
i = i * static_cast<IntType>(8) + static_cast<IntType>(c - '0');
} while (++p_begin != p_end);
return true;
}
//
// scalar --> hexadecimals
//
/// @brief Converts a scalar into hexadecimals. This function executes bounds check to avoid overflow.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @tparam IntType The output integer type. It can be either signed or unsigned.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename IntType>
inline bool atoi_hex(const char* p_begin, const char* p_end, IntType& i) noexcept {
static_assert(
is_non_bool_integral<IntType>::value, "atoi_hex() accepts non-boolean integral types as an output type");
if FK_YAML_UNLIKELY (p_begin == p_end) {
return false;
}
using conv_limits_type = conv_limits<sizeof(IntType), std::is_signed<IntType>::value>;
const auto len = static_cast<std::size_t>(p_end - p_begin);
if FK_YAML_UNLIKELY (!conv_limits_type::check_if_hexs_safe(p_begin, len)) {
return false;
}
i = 0;
do {
// NOLINTBEGIN(bugprone-misplaced-widening-cast)
const char c = *p_begin;
IntType ci = 0;
if ('0' <= c && c <= '9') {
ci = static_cast<IntType>(c - '0');
}
else if ('A' <= c && c <= 'F') {
ci = static_cast<IntType>(c - 'A' + 10);
}
else if ('a' <= c && c <= 'f') {
ci = static_cast<IntType>(c - 'a' + 10);
}
else {
return false;
}
i = i * static_cast<IntType>(16) + ci;
// NOLINTEND(bugprone-misplaced-widening-cast)
} while (++p_begin != p_end);
return true;
}
//
// atoi() & itoa()
//
/// @brief Converts a scalar into integers. This function executes bounds check to avoid overflow/underflow.
/// @tparam CharItr The type of char iterators. Its value type must be char (maybe cv-qualified).
/// @tparam IntType The output integer type. It can be either signed or unsigned.
/// @param begin The iterator to the first element of the scalar.
/// @param end The iterator to the past-the-end element of the scalar.
/// @param i The output integer value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename CharItr, typename IntType>
inline bool atoi(CharItr begin, CharItr end, IntType& i) noexcept {
static_assert(is_iterator_of<CharItr, char>::value, "atoi() accepts iterators for char type");
static_assert(is_non_bool_integral<IntType>::value, "atoi() accepts non-boolean integral types as an output type");
if FK_YAML_UNLIKELY (begin == end) {
return false;
}
const auto len = static_cast<uint32_t>(std::distance(begin, end));
const char* p_begin = &*begin;
const char* p_end = p_begin + len;
const char first = *begin;
if (first == '+') {
return atoi_dec_pos(p_begin + 1, p_end, i);
}
if (first == '-') {
if (!std::numeric_limits<IntType>::is_signed) {
return false;
}
const bool success = atoi_dec_neg(p_begin + 1, p_end, i);
if (success) {
i *= static_cast<IntType>(-1);
}
return success;
}
if (first != '0') {
return atoi_dec_pos(p_begin, p_end, i);
}
if (p_begin + 1 != p_end) {
switch (*(p_begin + 1)) {
case 'o':
return atoi_oct(p_begin + 2, p_end, i);
case 'x':
return atoi_hex(p_begin + 2, p_end, i);
default:
// The YAML spec doesn't allow decimals starting with 0.
return false;
}
}
i = 0;
return true;
}
///////////////////////////
// scalar <--> float //
///////////////////////////
/// @brief Set an infinite `float` value based on the given signedness.
/// @param f The output `float` value holder.
/// @param sign Whether the infinite value should be positive or negative.
inline void set_infinity(float& f, const float sign) noexcept {
f = std::numeric_limits<float>::infinity() * sign;
}
/// @brief Set an infinite `double` value based on the given signedness.
/// @param f The output `double` value holder.
/// @param sign Whether the infinite value should be positive or negative.
inline void set_infinity(double& f, const double sign) noexcept {
f = std::numeric_limits<double>::infinity() * sign;
}
/// @brief Set a NaN `float` value.
/// @param f The output `float` value holder.
inline void set_nan(float& f) noexcept {
f = std::nanf("");
}
/// @brief Set a NaN `double` value.
/// @param f The output `double` value holder.
inline void set_nan(double& f) noexcept {
f = std::nan("");
}
#if FK_YAML_HAS_TO_CHARS
/// @brief Converts a scalar into a floating point value.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param f The output floating point value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename FloatType>
inline bool atof_impl(const char* p_begin, const char* p_end, FloatType& f) noexcept {
static_assert(std::is_floating_point_v<FloatType>, "atof_impl() accepts floating point types as an output type");
if (auto [ptr, ec] = std::from_chars(p_begin, p_end, f); ec == std::errc {}) {
return ptr == p_end;
}
return false;
}
#else
/// @brief Converts a scalar into a `float` value.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param f The output `float` value holder.
/// @return true if the conversion completes successfully, false otherwise.
inline bool atof_impl(const char* p_begin, const char* p_end, float& f) {
std::size_t idx = 0;
f = std::stof(std::string(p_begin, p_end), &idx);
return idx == static_cast<std::size_t>(p_end - p_begin);
}
/// @brief Converts a scalar into a `double` value.
/// @warning `p_begin` and `p_end` must not be null. Validate them before calling this function.
/// @param p_begin The pointer to the first element of the scalar.
/// @param p_end The pointer to the past-the-end element of the scalar.
/// @param f The output `double` value holder.
/// @return true if the conversion completes successfully, false otherwise.
inline bool atof_impl(const char* p_begin, const char* p_end, double& f) {
std::size_t idx = 0;
f = std::stod(std::string(p_begin, p_end), &idx);
return idx == static_cast<std::size_t>(p_end - p_begin);
}
#endif // FK_YAML_HAS_TO_CHARS
/// @brief Converts a scalar into a floating point value.
/// @tparam CharItr The type of char iterators. Its value type must be char (maybe cv-qualified).
/// @tparam FloatType The output floating point value type.
/// @param begin The iterator to the first element of the scalar.
/// @param end The iterator to the past-the-end element of the scalar.
/// @param f The output floating point value holder.
/// @return true if the conversion completes successfully, false otherwise.
template <typename CharItr, typename FloatType>
inline bool atof(CharItr begin, CharItr end, FloatType& f) noexcept(noexcept(atof_impl(&*begin, &*begin, f))) {
static_assert(is_iterator_of<CharItr, char>::value, "atof() accepts iterators for char type");
static_assert(std::is_floating_point<FloatType>::value, "atof() accepts floating point types as an output type");
if FK_YAML_UNLIKELY (begin == end) {
return false;
}
const auto len = static_cast<uint32_t>(std::distance(begin, end));
const char* p_begin = &*begin;
const char* p_end = p_begin + len;
if (*p_begin == '-' || *p_begin == '+') {
if (len == 5) {
const char* p_from_second = p_begin + 1;
const bool is_inf = (std::strncmp(p_from_second, ".inf", 4) == 0) ||
(std::strncmp(p_from_second, ".Inf", 4) == 0) ||
(std::strncmp(p_from_second, ".INF", 4) == 0);
if (is_inf) {
set_infinity(f, *p_begin == '-' ? static_cast<FloatType>(-1.) : static_cast<FloatType>(1.));
return true;
}
}
if (*p_begin == '+') {
// Skip the positive sign since it's sometimes not recognized as part of float value.
++p_begin;
}
}
else if (len == 4) {
const bool is_inf = (std::strncmp(p_begin, ".inf", 4) == 0) || (std::strncmp(p_begin, ".Inf", 4) == 0) ||
(std::strncmp(p_begin, ".INF", 4) == 0);
if (is_inf) {
set_infinity(f, static_cast<FloatType>(1.));
return true;
}
const bool is_nan = (std::strncmp(p_begin, ".nan", 4) == 0) || (std::strncmp(p_begin, ".NaN", 4) == 0) ||
(std::strncmp(p_begin, ".NAN", 4) == 0);
if (is_nan) {
set_nan(f);
return true;
}
}
#if FK_YAML_HAS_TO_CHARS
return atof_impl(p_begin, p_end, f);
#else
bool success = false;
try {
success = atof_impl(p_begin, p_end, f);
}
catch (const std::exception& /*unused*/) {
success = false;
}
return success;
#endif
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_CONVERSIONS_SCALAR_CONV_HPP */
// #include <fkYAML/detail/encodings/yaml_escaper.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP
#define FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/detail/encodings/utf_encodings.hpp>
// #include <fkYAML/exception.hpp>
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<char>(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;
break;
case 0x11:
escaped += "\\u0011";
is_escaped = true;
break;
case 0x12:
escaped += "\\u0012";
is_escaped = true;
break;
case 0x13:
escaped += "\\u0013";
is_escaped = true;
break;
case 0x14:
escaped += "\\u0014";
is_escaped = true;
break;
case 0x15:
escaped += "\\u0015";
is_escaped = true;
break;
case 0x16:
escaped += "\\u0016";
is_escaped = true;
break;
case 0x17:
escaped += "\\u0017";
is_escaped = true;
break;
case 0x18:
escaped += "\\u0018";
is_escaped = true;
break;
case 0x19:
escaped += "\\u0019";
is_escaped = true;
break;
case 0x1A:
escaped += "\\u001A";
is_escaped = true;
break;
case 0x1B:
escaped += "\\e";
is_escaped = true;
break;
case 0x1C:
escaped += "\\u001C";
is_escaped = true;
break;
case 0x1D:
escaped += "\\u001D";
is_escaped = true;
break;
case 0x1E:
escaped += "\\u001E";
is_escaped = true;
break;
case 0x1F:
escaped += "\\u001F";
is_escaped = true;
break;
case '\"':
escaped += "\\\"";
is_escaped = true;
break;
case '\\':
escaped += "\\\\";
is_escaped = true;
break;
default:
const std::ptrdiff_t diff = static_cast<int>(std::distance(begin, end));
if (diff > 1) {
if (*begin == static_cast<char>(0xC2u) && *(begin + 1) == static_cast<char>(0x85u)) {
escaped += "\\N";
std::advance(begin, 1);
is_escaped = true;
break;
}
if (*begin == static_cast<char>(0xC2u) && *(begin + 1) == static_cast<char>(0xA0u)) {
escaped += "\\_";
std::advance(begin, 1);
is_escaped = true;
break;
}
if (diff > 2) {
if (*begin == static_cast<char>(0xE2u) && *(begin + 1) == static_cast<char>(0x80u) &&
*(begin + 2) == static_cast<char>(0xA8u)) {
escaped += "\\L";
std::advance(begin, 2);
is_escaped = true;
break;
}
if (*begin == static_cast<char>(0xE2u) && *(begin + 1) == static_cast<char>(0x80u) &&
*(begin + 2) == static_cast<char>(0xA9u)) {
escaped += "\\P";
std::advance(begin, 2);
is_escaped = true;
break;
}
}
}
escaped += *begin;
break;
}
}
return escaped;
} // LCOV_EXCL_LINE
private:
static bool convert_hexchar_to_byte(char source, uint8_t& byte) {
if ('0' <= source && source <= '9') {
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
byte = static_cast<uint8_t>(source - '0');
return true;
}
if ('A' <= source && source <= 'F') {
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
byte = static_cast<uint8_t>(source - 'A' + 10);
return true;
}
if ('a' <= source && source <= 'f') {
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
byte = static_cast<uint8_t>(source - 'a' + 10);
return true;
}
// The given character is not hexadecimal.
return false;
}
static bool extract_codepoint(const char*& begin, const char* end, int bytes_to_read, char32_t& codepoint) {
const bool has_enough_room = static_cast<int>(std::distance(begin, end)) >= (bytes_to_read - 1);
if (!has_enough_room) {
return false;
}
const int read_size = bytes_to_read * 2;
uint8_t byte {0};
codepoint = 0;
for (int i = read_size - 1; i >= 0; i--) {
const bool is_valid = convert_hexchar_to_byte(*++begin, byte);
if (!is_valid) {
return false;
}
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
codepoint |= static_cast<char32_t>(byte << (4 * i));
}
return true;
}
static void unescape_escaped_unicode(char32_t codepoint, std::string& buff) {
// the inner curly braces are necessary to build with older compilers.
std::array<uint8_t, 4> encode_buff {{}};
uint32_t encoded_size {0};
utf8::from_utf32(codepoint, encode_buff, encoded_size);
buff.append(reinterpret_cast<char*>(encode_buff.data()), encoded_size);
}
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP */
// #include <fkYAML/detail/input/block_scalar_header.hpp>
// #include <fkYAML/detail/input/scalar_scanner.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_SCANNER_HPP
#define FK_YAML_DETAIL_INPUT_SCALAR_SCANNER_HPP
#include <cstring>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/node_type.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief The class which detects a scalar value type by scanning contents.
class scalar_scanner {
public:
/// @brief Detects a scalar value type by scanning the contents ranged by the given iterators.
/// @param begin The iterator to the first element of the scalar.
/// @param end The iterator to the past-the-end element of the scalar.
/// @return A detected scalar value type.
static node_type scan(const char* begin, const char* end) noexcept {
if (begin == end) {
return node_type::STRING;
}
const auto len = static_cast<uint32_t>(std::distance(begin, end));
if (len > 5) {
return scan_possible_number_token(begin, len);
}
const char* p_begin = &*begin;
switch (len) {
case 1:
if (*p_begin == '~') {
return node_type::NULL_OBJECT;
}
break;
case 4:
switch (*p_begin) {
case 'n':
// no possible case of begin a number otherwise.
return (std::strncmp(p_begin + 1, "ull", 3) == 0) ? node_type::NULL_OBJECT : node_type::STRING;
case 'N':
// no possible case of begin a number otherwise.
return ((std::strncmp(p_begin + 1, "ull", 3) == 0) || (std::strncmp(p_begin + 1, "ULL", 3) == 0))
? node_type::NULL_OBJECT
: node_type::STRING;
case 't':
// no possible case of being a number otherwise.
return (std::strncmp(p_begin + 1, "rue", 3) == 0) ? node_type::BOOLEAN : node_type::STRING;
case 'T':
// no possible case of being a number otherwise.
return ((std::strncmp(p_begin + 1, "rue", 3) == 0) || (std::strncmp(p_begin + 1, "RUE", 3) == 0))
? node_type::BOOLEAN
: node_type::STRING;
case '.': {
const char* p_from_second = p_begin + 1;
const bool is_inf_or_nan_scalar =
(std::strncmp(p_from_second, "inf", 3) == 0) || (std::strncmp(p_from_second, "Inf", 3) == 0) ||
(std::strncmp(p_from_second, "INF", 3) == 0) || (std::strncmp(p_from_second, "nan", 3) == 0) ||
(std::strncmp(p_from_second, "NaN", 3) == 0) || (std::strncmp(p_from_second, "NAN", 3) == 0);
if (is_inf_or_nan_scalar) {
return node_type::FLOAT;
}
// maybe a number.
break;
}
default:
break;
}
break;
case 5:
switch (*p_begin) {
case 'f':
// no possible case of being a number otherwise.
return (std::strncmp(p_begin + 1, "alse", 4) == 0) ? node_type::BOOLEAN : node_type::STRING;
case 'F':
// no possible case of being a number otherwise.
return ((std::strncmp(p_begin + 1, "alse", 4) == 0) || (std::strncmp(p_begin + 1, "ALSE", 4) == 0))
? node_type::BOOLEAN
: node_type::STRING;
case '+':
case '-':
if (*(p_begin + 1) == '.') {
const char* p_from_third = p_begin + 2;
const bool is_min_inf = (std::strncmp(p_from_third, "inf", 3) == 0) ||
(std::strncmp(p_from_third, "Inf", 3) == 0) ||
(std::strncmp(p_from_third, "INF", 3) == 0);
if (is_min_inf) {
return node_type::FLOAT;
}
}
// maybe a number.
break;
default:
break;
}
break;
default:
break;
}
return scan_possible_number_token(begin, len);
}
private:
/// @brief Detects a scalar value type from the contents (possibly an integer or a floating-point value).
/// @param itr The iterator to the first element of the scalar.
/// @param len The length of the scalar contents.
/// @return A detected scalar value type.
static node_type scan_possible_number_token(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
switch (*itr) {
case '-':
return (len > 1) ? scan_negative_number(++itr, --len) : node_type::STRING;
case '+':
return (len > 1) ? scan_decimal_number(++itr, --len) : node_type::STRING;
case '.':
// some integer(s) required after the decimal point as a floating point value.
return (len > 1) ? scan_after_decimal_point(++itr, --len) : node_type::STRING;
case '0':
return (len > 1) ? scan_after_zero_at_first(++itr, --len) : node_type::INTEGER;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return (len > 1) ? scan_decimal_number(++itr, --len) : node_type::INTEGER;
default:
return node_type::STRING;
}
}
/// @brief Detects a scalar value type by scanning the contents right after the negative sign.
/// @param itr The iterator to the past-the-negative-sign element of the scalar.
/// @param len The length of the scalar contents left unscanned.
/// @return A detected scalar value type.
static node_type scan_negative_number(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
if (is_digit(*itr)) {
return (len > 1) ? scan_decimal_number(++itr, --len) : node_type::INTEGER;
}
if (*itr == '.') {
// some integer(s) required after "-." as a floating point value.
return (len > 1) ? scan_after_decimal_point(++itr, --len) : node_type::STRING;
}
return node_type::STRING;
}
/// @brief Detects a scalar value type by scanning the contents right after the beginning 0.
/// @param itr The iterator to the past-the-zero element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_after_zero_at_first(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
if (is_digit(*itr)) {
// a token consisting of the beginning '0' and some following numbers, e.g., `0123`, is not an integer
// according to https://yaml.org/spec/1.2.2/#10213-integer.
return node_type::STRING;
}
switch (*itr) {
case '.':
// 0 can be omitted after `0.`.
return (len > 1) ? scan_after_decimal_point(++itr, --len) : node_type::FLOAT;
case 'e':
case 'E':
// some integer(s) required after the exponent sign as a floating point value.
return (len > 1) ? scan_after_exponent(++itr, --len) : node_type::STRING;
case 'o':
return (len > 1) ? scan_octal_number(++itr, --len) : node_type::STRING;
case 'x':
return (len > 1) ? scan_hexadecimal_number(++itr, --len) : node_type::STRING;
default:
return node_type::STRING;
}
}
/// @brief Detects a scalar value type by scanning the contents part starting with a decimal.
/// @param itr The iterator to the beginning decimal element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_decimal_number(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
if (is_digit(*itr)) {
return (len > 1) ? scan_decimal_number(++itr, --len) : node_type::INTEGER;
}
switch (*itr) {
case '.': {
// 0 can be omitted after the decimal point
return (len > 1) ? scan_after_decimal_point(++itr, --len) : node_type::FLOAT;
}
case 'e':
case 'E':
// some integer(s) required after the exponent
return (len > 1) ? scan_after_exponent(++itr, --len) : node_type::STRING;
default:
return node_type::STRING;
}
}
/// @brief Detects a scalar value type by scanning the contents right after a decimal point.
/// @param itr The iterator to the past-the-decimal-point element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_after_decimal_point(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
for (uint32_t i = 0; i < len; i++) {
const char c = *itr++;
if (is_digit(c)) {
continue;
}
if (c == 'e' || c == 'E') {
if (i == len - 1) {
// some integer(s) required after the exponent
return node_type::STRING;
}
return scan_after_exponent(itr, len - i - 1);
}
return node_type::STRING;
}
return node_type::FLOAT;
}
/// @brief Detects a scalar value type by scanning the contents right after the exponent prefix ("e" or "E").
/// @param itr The iterator to the past-the-exponent-prefix element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_after_exponent(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
const char c = *itr;
if (c == '+' || c == '-') {
if (len == 1) {
// some integer(s) required after the sign.
return node_type::STRING;
}
++itr;
--len;
}
for (uint32_t i = 0; i < len; i++) {
if (!is_digit(*itr++)) {
return node_type::STRING;
}
}
return node_type::FLOAT;
}
/// @brief Detects a scalar value type by scanning the contents assuming octal numbers.
/// @param itr The iterator to the octal-number element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_octal_number(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
switch (*itr) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
return (len > 1) ? scan_octal_number(++itr, --len) : node_type::INTEGER;
default:
return node_type::STRING;
}
}
/// @brief Detects a scalar value type by scanning the contents assuming hexadecimal numbers.
/// @param itr The iterator to the hexadecimal-number element of the scalar.
/// @param len The length of the scalar left unscanned.
/// @return A detected scalar value type.
static node_type scan_hexadecimal_number(const char* itr, uint32_t len) noexcept {
FK_YAML_ASSERT(len > 0);
if (is_xdigit(*itr)) {
return (len > 1) ? scan_hexadecimal_number(++itr, --len) : node_type::INTEGER;
}
return node_type::STRING;
}
/// @brief Check if the given character is a digit.
/// @note This function is needed to avoid assertion failures in `std::isdigit()` especially when compiled with
/// MSVC.
/// @param c A character to be checked.
/// @return true if the given character is a digit, false otherwise.
static bool is_digit(char c) {
return ('0' <= c && c <= '9');
}
/// @brief Check if the given character is a hex-digit.
/// @note This function is needed to avoid assertion failures in `std::isxdigit()` especially when compiled with
/// MSVC.
/// @param c A character to be checked.
/// @return true if the given character is a hex-digit, false otherwise.
static bool is_xdigit(char c) {
return (('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'));
}
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_SCALAR_SCANNER_HPP */
// #include <fkYAML/detail/input/tag_t.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_TAG_T_HPP
#define FK_YAML_DETAIL_INPUT_TAG_T_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Definition of YAML tag types.
enum class tag_t : std::uint8_t {
NONE, //!< Represents a non-specific tag "?".
NON_SPECIFIC, //!< Represents a non-specific tag "!".
CUSTOM_TAG, //!< Represents a custom tag
SEQUENCE, //!< Represents a sequence tag.
MAPPING, //!< Represents a mapping tag.
NULL_VALUE, //!< Represents a null value tag.
BOOLEAN, //!< Represents a boolean tag.
INTEGER, //!< Represents an integer type
FLOATING_NUMBER, //!< Represents a floating point number tag.
STRING, //!< Represents a string tag.
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_TAG_T_HPP */
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/str_view.hpp>
// #include <fkYAML/detail/types/lexical_token_t.hpp>
// #include <fkYAML/exception.hpp>
// #include <fkYAML/node_type.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A parser for YAML scalars.
/// @tparam BasicNodeType A type of the container for parsed YAML scalars.
template <typename BasicNodeType>
class scalar_parser {
static_assert(is_basic_node<BasicNodeType>::value, "scalar_parser only accepts basic_node<...>");
public:
using basic_node_type = BasicNodeType;
private:
/** A type for boolean node values. */
using boolean_type = typename basic_node_type::boolean_type;
/** A type for integer node values. */
using integer_type = typename basic_node_type::integer_type;
/** A type for floating point node values. */
using float_number_type = typename basic_node_type::float_number_type;
/** A type for string node values. */
using string_type = typename basic_node_type::string_type;
public:
/// @brief Constructs a new scalar_parser object.
/// @param line Current line.
/// @param indent Current indentation.
scalar_parser(uint32_t line, uint32_t indent) noexcept
: m_line(line),
m_indent(indent) {
}
/// @brief Destroys a scalar_parser object.
~scalar_parser() noexcept = default;
// std::string's copy constructor/assignment operator may throw a exception.
scalar_parser(const scalar_parser&) = default;
scalar_parser& operator=(const scalar_parser&) = default;
scalar_parser(scalar_parser&&) noexcept = default;
scalar_parser& operator=(scalar_parser&&) noexcept(std::is_nothrow_move_assignable<std::string>::value) = default;
/// @brief Parses a token into a flow scalar (either plain, single quoted or double quoted)
/// @param lex_type Lexical token type for the scalar.
/// @param tag_type Tag type for the scalar.
/// @param token Scalar contents.
/// @return Parsed YAML flow scalar object.
basic_node_type parse_flow(lexical_token_t lex_type, tag_t tag_type, str_view token) {
FK_YAML_ASSERT(
lex_type == lexical_token_t::PLAIN_SCALAR || lex_type == lexical_token_t::SINGLE_QUOTED_SCALAR ||
lex_type == lexical_token_t::DOUBLE_QUOTED_SCALAR);
FK_YAML_ASSERT(tag_type != tag_t::SEQUENCE && tag_type != tag_t::MAPPING);
token = parse_flow_scalar_token(lex_type, token);
const node_type value_type = decide_value_type(lex_type, tag_type, token);
return create_scalar_node(value_type, tag_type, token);
}
/// @brief Parses a token into a block scalar (either literal or folded)
/// @param lex_type Lexical token type for the scalar.
/// @param tag_type Tag type for the scalar.
/// @param token Scalar contents.
/// @param header Block scalar header information.
/// @return Parsed YAML block scalar object.
basic_node_type parse_block(
lexical_token_t lex_type, tag_t tag_type, str_view token, const block_scalar_header& header) {
FK_YAML_ASSERT(
lex_type == lexical_token_t::BLOCK_LITERAL_SCALAR || lex_type == lexical_token_t::BLOCK_FOLDED_SCALAR);
FK_YAML_ASSERT(tag_type != tag_t::SEQUENCE && tag_type != tag_t::MAPPING);
if (lex_type == lexical_token_t::BLOCK_LITERAL_SCALAR) {
token = parse_block_literal_scalar(token, header);
}
else {
token = parse_block_folded_scalar(token, header);
}
const node_type value_type = decide_value_type(lex_type, tag_type, token);
return create_scalar_node(value_type, tag_type, token);
}
private:
/// @brief Parses a token into a flow scalar contents.
/// @param lex_type Lexical token type for the scalar.
/// @param token Scalar contents.
/// @return View into the parsed scalar contents.
str_view parse_flow_scalar_token(lexical_token_t lex_type, str_view token) {
switch (lex_type) {
case lexical_token_t::PLAIN_SCALAR:
token = parse_plain_scalar(token);
break;
case lexical_token_t::SINGLE_QUOTED_SCALAR:
token = parse_single_quoted_scalar(token);
break;
case lexical_token_t::DOUBLE_QUOTED_SCALAR:
token = parse_double_quoted_scalar(token);
break;
default: // LCOV_EXCL_LINE
unreachable(); // LCOV_EXCL_LINE
}
return token;
}
/// @brief Parses plain scalar contents.
/// @param token Scalar contents.
/// @return View into the parsed scalar contents.
str_view parse_plain_scalar(str_view token) noexcept {
// plain scalars cannot be empty.
FK_YAML_ASSERT(!token.empty());
std::size_t newline_pos = token.find('\n');
if (newline_pos == str_view::npos) {
return token;
}
m_use_owned_buffer = true;
if (m_buffer.capacity() < token.size()) {
m_buffer.reserve(token.size());
}
do {
process_line_folding(token, newline_pos);
newline_pos = token.find('\n');
} while (newline_pos != str_view::npos);
m_buffer.append(token.begin(), token.size());
return {m_buffer};
}
/// @brief Parses single quoted scalar contents.
/// @param token Scalar contents.
/// @return View into the parsed scalar contents.
str_view parse_single_quoted_scalar(str_view token) noexcept {
if (token.empty()) {
return token;
}
constexpr str_view filter {"\'\n"};
std::size_t pos = token.find_first_of(filter);
if (pos == str_view::npos) {
return token;
}
m_use_owned_buffer = true;
if (m_buffer.capacity() < token.size()) {
m_buffer.reserve(token.size());
}
do {
FK_YAML_ASSERT(pos < token.size());
FK_YAML_ASSERT(token[pos] == '\'' || token[pos] == '\n');
if (token[pos] == '\'') {
// unescape escaped single quote. ('' -> ')
FK_YAML_ASSERT(pos + 1 < token.size());
m_buffer.append(token.begin(), token.begin() + (pos + 1));
token.remove_prefix(pos + 2); // move next to the escaped single quote.
}
else {
process_line_folding(token, pos);
}
pos = token.find_first_of(filter);
} while (pos != str_view::npos);
if (!token.empty()) {
m_buffer.append(token.begin(), token.size());
}
return {m_buffer};
}
/// @brief Parses double quoted scalar contents.
/// @param token Scalar contents.
/// @return View into the parsed scalar contents.
str_view parse_double_quoted_scalar(str_view token) {
if (token.empty()) {
return token;
}
constexpr str_view filter {"\\\n"};
std::size_t pos = token.find_first_of(filter);
if (pos == str_view::npos) {
return token;
}
m_use_owned_buffer = true;
if (m_buffer.capacity() < token.size()) {
m_buffer.reserve(token.size());
}
do {
FK_YAML_ASSERT(pos < token.size());
FK_YAML_ASSERT(token[pos] == '\\' || token[pos] == '\n');
if (token[pos] == '\\') {
FK_YAML_ASSERT(pos + 1 < token.size());
m_buffer.append(token.begin(), token.begin() + pos);
if (token[pos + 1] != '\n') {
token.remove_prefix(pos);
const char* p_escape_begin = token.begin();
const bool is_valid_escaping = yaml_escaper::unescape(p_escape_begin, token.end(), m_buffer);
if FK_YAML_UNLIKELY (!is_valid_escaping) {
throw parse_error(
"Unsupported escape sequence is found in a double quoted scalar.", m_line, m_indent);
}
// `p_escape_begin` points to the last element of the escape sequence.
token.remove_prefix((p_escape_begin - token.begin()) + 1);
}
else {
std::size_t non_space_pos = token.find_first_not_of(" \t", pos + 2);
if (non_space_pos == str_view::npos) {
non_space_pos = token.size();
}
token.remove_prefix(non_space_pos);
}
}
else {
process_line_folding(token, pos);
}
pos = token.find_first_of(filter);
} while (pos != str_view::npos);
if (!token.empty()) {
m_buffer.append(token.begin(), token.size());
}
return {m_buffer};
}
/// @brief Parses block literal scalar contents.
/// @param token Scalar contents.
/// @param header Block scalar header information.
/// @return View into the parsed scalar contents.
str_view parse_block_literal_scalar(str_view token, const block_scalar_header& header) {
if FK_YAML_UNLIKELY (token.empty()) {
return token;
}
m_use_owned_buffer = true;
m_buffer.reserve(token.size());
std::size_t cur_line_begin_pos = 0;
do {
bool has_newline_at_end = true;
std::size_t cur_line_end_pos = token.find('\n', cur_line_begin_pos);
if (cur_line_end_pos == str_view::npos) {
has_newline_at_end = false;
cur_line_end_pos = token.size();
}
const std::size_t line_size = cur_line_end_pos - cur_line_begin_pos;
const str_view line = token.substr(cur_line_begin_pos, line_size);
if (line.size() > header.indent) {
m_buffer.append(line.begin() + header.indent, line.end());
}
if (!has_newline_at_end) {
break;
}
m_buffer.push_back('\n');
cur_line_begin_pos = cur_line_end_pos + 1;
} while (cur_line_begin_pos < token.size());
process_chomping(header.chomp);
return {m_buffer};
}
/// @brief Parses block folded scalar contents.
/// @param token Scalar contents.
/// @param header Block scalar header information.
/// @return View into the parsed scalar contents.
str_view parse_block_folded_scalar(str_view token, const block_scalar_header& header) {
if FK_YAML_UNLIKELY (token.empty()) {
return token;
}
m_use_owned_buffer = true;
m_buffer.reserve(token.size());
constexpr str_view white_space_filter {" \t"};
std::size_t cur_line_begin_pos = 0;
bool has_newline_at_end = true;
bool can_be_folded = false;
do {
std::size_t cur_line_end_pos = token.find('\n', cur_line_begin_pos);
if (cur_line_end_pos == str_view::npos) {
has_newline_at_end = false;
cur_line_end_pos = token.size();
}
const std::size_t line_size = cur_line_end_pos - cur_line_begin_pos;
const str_view line = token.substr(cur_line_begin_pos, line_size);
const bool is_empty = line.find_first_not_of(white_space_filter) == str_view::npos;
if (line.size() <= header.indent) {
// A less-indented line is turned into a newline.
m_buffer.push_back('\n');
can_be_folded = false;
}
else if (is_empty) {
// more-indented empty lines are not folded.
m_buffer.push_back('\n');
m_buffer.append(line.begin() + header.indent, line.end());
m_buffer.push_back('\n');
}
else {
const std::size_t non_space_pos = line.find_first_not_of(white_space_filter);
const bool is_more_indented = (non_space_pos != str_view::npos) && (non_space_pos > header.indent);
if (can_be_folded) {
if (is_more_indented) {
// The content line right before more-indented lines is not folded.
m_buffer.push_back('\n');
}
else {
m_buffer.push_back(' ');
}
can_be_folded = false;
}
m_buffer.append(line.begin() + header.indent, line.end());
if (is_more_indented && has_newline_at_end) {
// more-indented lines are not folded.
m_buffer.push_back('\n');
}
else {
can_be_folded = true;
}
}
if (!has_newline_at_end) {
break;
}
cur_line_begin_pos = cur_line_end_pos + 1;
} while (cur_line_begin_pos < token.size());
if (has_newline_at_end && can_be_folded) {
// The final content line break are not folded.
m_buffer.push_back('\n');
}
process_chomping(header.chomp);
return {m_buffer};
}
/// @brief Discards final content line break and trailing empty lines depending on the given chomping type.
/// @param chomp Chomping method type.
void process_chomping(chomping_indicator_t chomp) {
switch (chomp) {
case chomping_indicator_t::STRIP: {
const std::size_t content_end_pos = m_buffer.find_last_not_of('\n');
if (content_end_pos == std::string::npos) {
// if the scalar has no content line, all lines are considered as trailing empty lines.
m_buffer.clear();
break;
}
if (content_end_pos == m_buffer.size() - 1) {
// no last content line break nor trailing empty lines.
break;
}
// remove the last content line break and all trailing empty lines.
m_buffer.erase(content_end_pos + 1);
break;
}
case chomping_indicator_t::CLIP: {
const std::size_t content_end_pos = m_buffer.find_last_not_of('\n');
if (content_end_pos == std::string::npos) {
// if the scalar has no content line, all lines are considered as trailing empty lines.
m_buffer.clear();
break;
}
if (content_end_pos == m_buffer.size() - 1) {
// no trailing empty lines
break;
}
// remove all trailing empty lines.
m_buffer.erase(content_end_pos + 2);
break;
}
case chomping_indicator_t::KEEP:
break;
}
}
/// @brief Applies line folding to flow scalar contents.
/// @param token Flow scalar contents.
/// @param newline_pos Position of the target newline code.
void process_line_folding(str_view& token, std::size_t newline_pos) noexcept {
// discard trailing white spaces which precedes the line break in the current line.
const std::size_t last_non_space_pos = token.substr(0, newline_pos + 1).find_last_not_of(" \t");
if (last_non_space_pos == str_view::npos) {
m_buffer.append(token.begin(), newline_pos);
}
else {
m_buffer.append(token.begin(), last_non_space_pos + 1);
}
token.remove_prefix(newline_pos + 1); // move next to the LF
uint32_t empty_line_counts = 0;
do {
const std::size_t non_space_pos = token.find_first_not_of(" \t");
if (non_space_pos == str_view::npos) {
// Line folding ignores trailing spaces.
token.remove_prefix(token.size());
break;
}
if (token[non_space_pos] != '\n') {
token.remove_prefix(non_space_pos);
break;
}
token.remove_prefix(non_space_pos + 1);
++empty_line_counts;
} while (true);
if (empty_line_counts > 0) {
m_buffer.append(empty_line_counts, '\n');
}
else {
m_buffer.push_back(' ');
}
}
/// @brief Decides scalar value type based on the lexical/tag types and scalar contents.
/// @param lex_type Lexical token type for the scalar.
/// @param tag_type Tag type for the scalar.
/// @param token Scalar contents.
/// @return Scalar value type.
node_type decide_value_type(lexical_token_t lex_type, tag_t tag_type, str_view token) const noexcept {
node_type value_type {node_type::STRING};
if (lex_type == lexical_token_t::PLAIN_SCALAR) {
value_type = scalar_scanner::scan(token.begin(), token.end());
}
switch (tag_type) {
case tag_t::NULL_VALUE:
value_type = node_type::NULL_OBJECT;
break;
case tag_t::BOOLEAN:
value_type = node_type::BOOLEAN;
break;
case tag_t::INTEGER:
value_type = node_type::INTEGER;
break;
case tag_t::FLOATING_NUMBER:
value_type = node_type::FLOAT;
break;
case tag_t::STRING:
case tag_t::NON_SPECIFIC:
// scalars with the non-specific tag is resolved to a string tag.
// See the "Non-Specific Tags" section in https://yaml.org/spec/1.2.2/#691-node-tags.
value_type = node_type::STRING;
break;
case tag_t::NONE:
case tag_t::CUSTOM_TAG:
default:
break;
}
return value_type;
}
/// @brief Creates YAML scalar object based on the value type and contents.
/// @param type Scalar value type.
/// @param token Scalar contents.
/// @return A YAML scalar object.
basic_node_type create_scalar_node(node_type val_type, tag_t tag_type, str_view token) {
switch (val_type) {
case node_type::NULL_OBJECT: {
std::nullptr_t null = nullptr;
const bool converted = detail::aton(token.begin(), token.end(), null);
if FK_YAML_UNLIKELY (!converted) {
throw parse_error("Failed to convert a scalar to a null.", m_line, m_indent);
}
// The default basic_node object is a null scalar node.
return basic_node_type {};
}
case node_type::BOOLEAN: {
auto boolean = static_cast<boolean_type>(false);
const bool converted = detail::atob(token.begin(), token.end(), boolean);
if FK_YAML_UNLIKELY (!converted) {
throw parse_error("Failed to convert a scalar to a boolean.", m_line, m_indent);
}
return basic_node_type(boolean);
}
case node_type::INTEGER: {
integer_type integer = 0;
const bool converted = detail::atoi(token.begin(), token.end(), integer);
if FK_YAML_LIKELY (converted) {
return basic_node_type(integer);
}
if FK_YAML_UNLIKELY (tag_type == tag_t::INTEGER) {
throw parse_error("Failed to convert a scalar to an integer.", m_line, m_indent);
}
// conversion error from a scalar which is not tagged with !!int is recovered by treating it as a string
// scalar. See https://github.com/fktn-k/fkYAML/issues/428.
return basic_node_type(string_type(token.begin(), token.end()));
}
case node_type::FLOAT: {
float_number_type float_val = 0;
const bool converted = detail::atof(token.begin(), token.end(), float_val);
if FK_YAML_LIKELY (converted) {
return basic_node_type(float_val);
}
if FK_YAML_UNLIKELY (tag_type == tag_t::FLOATING_NUMBER) {
throw parse_error("Failed to convert a scalar to a floating point value", m_line, m_indent);
}
// conversion error from a scalar which is not tagged with !!float is recovered by treating it as a string
// scalar. See https://github.com/fktn-k/fkYAML/issues/428.
return basic_node_type(string_type(token.begin(), token.end()));
}
case node_type::STRING:
if (!m_use_owned_buffer) {
return basic_node_type(string_type(token.begin(), token.end()));
}
m_use_owned_buffer = false;
return basic_node_type(std::move(m_buffer));
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
/// Current line
uint32_t m_line {0};
/// Current indentation for the scalar
uint32_t m_indent {0};
/// Whether the parsed contents are stored in an owned buffer.
bool m_use_owned_buffer {false};
/// Owned buffer storage for parsing. This buffer is used when scalar contents need mutation.
std::string m_buffer;
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_SCALAR_PARSER_HPP */
// #include <fkYAML/detail/input/tag_resolver.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_TAG_RESOLVER_HPP
#define FK_YAML_DETAIL_INPUT_TAG_RESOLVER_HPP
#include <memory>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/detail/document_metainfo.hpp>
// #include <fkYAML/detail/input/tag_t.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/str_view.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
static constexpr str_view default_primary_handle_prefix {"!"};
static constexpr str_view default_secondary_handle_prefix {"tag:yaml.org,2002:"};
template <typename BasicNodeType>
class tag_resolver {
static_assert(is_basic_node<BasicNodeType>::value, "tag_resolver only accepts basic_node<...>.");
using doc_metainfo_type = document_metainfo<BasicNodeType>;
public:
/// @brief Resolve the input tag name into an expanded tag name prepended with a registered prefix.
/// @param tag The input tag name.
/// @return The type of a node deduced from the given tag name.
static tag_t resolve_tag(const str_view tag, const std::shared_ptr<doc_metainfo_type>& directives) {
const std::string normalized = normalize_tag_name(tag, directives);
return convert_to_tag_type(normalized);
}
private:
static std::string normalize_tag_name(const str_view tag, const std::shared_ptr<doc_metainfo_type>& directives) {
if FK_YAML_UNLIKELY (tag.empty()) {
throw invalid_tag("tag must not be empty.", "");
}
if FK_YAML_UNLIKELY (tag[0] != '!') {
throw invalid_tag("tag must start with \'!\'", std::string(tag.begin(), tag.end()).c_str());
}
if (tag.size() == 1) {
// Non-specific tag ("!") will be interpreted as one of the following:
// * tag:yaml.org,2002:seq
// * tag:yaml.org,2002:map
// * tag:yaml.org,2002:str
// See the "Non-Specific Tags" section in https://yaml.org/spec/1.2.2/#691-node-tags.
// The interpretation cannot take place here because the input lacks the corresponding value.
return {tag.begin(), tag.end()};
}
std::string normalized {"!<"};
switch (tag[1]) {
case '!': {
// handle a secondary tag handle (!!suffix -> !<[secondary][suffix]>)
const bool is_null_or_empty = !directives || directives->secondary_handle_prefix.empty();
if (is_null_or_empty) {
normalized.append(default_secondary_handle_prefix.begin(), default_secondary_handle_prefix.end());
}
else {
normalized += directives->secondary_handle_prefix;
}
const str_view body = tag.substr(2);
normalized.append(body.begin(), body.end());
break;
}
case '<':
if (tag[2] == '!') {
const bool is_null_or_empty = !directives || directives->primary_handle_prefix.empty();
if (is_null_or_empty) {
normalized.append(default_primary_handle_prefix.begin(), default_primary_handle_prefix.end());
}
else {
normalized += directives->primary_handle_prefix;
}
const str_view body = tag.substr(3);
return normalized.append(body.begin(), body.end());
}
// verbatim tags must be delivered as-is to the application.
// See https://yaml.org/spec/1.2.2/#691-node-tags for more details.
return {tag.begin(), tag.end()};
default: {
const std::size_t tag_end_pos = tag.find_first_of('!', 1);
// handle a named handle (!tag!suffix -> !<[tag][suffix]>)
if (tag_end_pos != std::string::npos) {
// there must be a non-empty suffix. (already checked by the lexer.)
FK_YAML_ASSERT(tag_end_pos < tag.size() - 1);
const bool is_null_or_empty = !directives || directives->named_handle_map.empty();
if FK_YAML_UNLIKELY (is_null_or_empty) {
throw invalid_tag(
"named handle has not been registered.", std::string(tag.begin(), tag.end()).c_str());
}
// find the extracted named handle in the map.
const str_view named_handle = tag.substr(0, tag_end_pos + 1);
auto named_handle_itr = directives->named_handle_map.find({named_handle.begin(), named_handle.end()});
auto end_itr = directives->named_handle_map.end();
if FK_YAML_UNLIKELY (named_handle_itr == end_itr) {
throw invalid_tag(
"named handle has not been registered.", std::string(tag.begin(), tag.end()).c_str());
}
// The YAML spec prohibits expanding the percent-encoded characters (%xx -> a UTF-8 byte).
// So no conversion takes place.
// See https://yaml.org/spec/1.2.2/#56-miscellaneous-characters for more details.
normalized += named_handle_itr->second;
const str_view body = tag.substr(tag_end_pos + 1);
normalized.append(body.begin(), body.end());
break;
}
// handle a primary tag handle (!suffix -> !<[primary][suffix]>)
const bool is_null_or_empty = !directives || directives->primary_handle_prefix.empty();
if (is_null_or_empty) {
normalized.append(default_primary_handle_prefix.begin(), default_primary_handle_prefix.end());
}
else {
normalized += directives->primary_handle_prefix;
}
const str_view body = tag.substr(1);
normalized.append(body.begin(), body.end());
break;
}
}
normalized += ">";
return normalized;
}
static tag_t convert_to_tag_type(const std::string& normalized) {
if (normalized == "!") {
return tag_t::NON_SPECIFIC;
}
if (normalized.size() < 24 /* size of !<tag:yaml.org,2002:xxx */) {
return tag_t::CUSTOM_TAG;
}
if (normalized.rfind("!<tag:yaml.org,2002:", 0) == std::string::npos) {
return tag_t::CUSTOM_TAG;
}
if (normalized == "!<tag:yaml.org,2002:seq>") {
return tag_t::SEQUENCE;
}
if (normalized == "!<tag:yaml.org,2002:map>") {
return tag_t::MAPPING;
}
if (normalized == "!<tag:yaml.org,2002:null>") {
return tag_t::NULL_VALUE;
}
if (normalized == "!<tag:yaml.org,2002:bool>") {
return tag_t::BOOLEAN;
}
if (normalized == "!<tag:yaml.org,2002:int>") {
return tag_t::INTEGER;
}
if (normalized == "!<tag:yaml.org,2002:float>") {
return tag_t::FLOATING_NUMBER;
}
if (normalized == "!<tag:yaml.org,2002:str>") {
return tag_t::STRING;
}
return tag_t::CUSTOM_TAG;
}
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_TAG_RESOLVER_HPP */
// #include <fkYAML/detail/meta/input_adapter_traits.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP
#define FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP
#include <type_traits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/detect.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
///////////////////////////////////////////
// Input Adapter API detection traits
///////////////////////////////////////////
/// @brief A type which represents get_buffer_view function.
/// @tparam T A target type.
template <typename T>
using get_buffer_view_fn_t = decltype(std::declval<T>().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 <typename InputAdapterType>
using has_get_buffer_view = is_detected<get_buffer_view_fn_t, InputAdapterType>;
////////////////////////////////
// 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 <typename T, typename = void>
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 <typename InputAdapterType>
struct is_input_adapter<InputAdapterType, enable_if_t<has_get_buffer_view<InputAdapterType>::value>> : std::true_type {
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP */
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/node_attrs.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_NODE_ATTRS_HPP
#define FK_YAML_DETAIL_NODE_ATTRS_HPP
#include <cstdint>
#include <limits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/node_type.hpp>
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<node_attr_t>::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
}
}
/// @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 <fkYAML/detail/node_property.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_NODE_PROPERTY_HPP
#define FK_YAML_DETAIL_NODE_PROPERTY_HPP
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
struct node_property {
/// The tag name property.
std::string tag {}; // NOLINT(readability-redundant-member-init) necessary for older compilers
/// The anchor name property.
std::string anchor {}; // NOLINT(readability-redundant-member-init) necessary for older compilers
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_NODE_PROPERTY_HPP */
// #include <fkYAML/detail/types/lexical_token_t.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A class which provides the feature of deserializing YAML documents.
/// @tparam BasicNodeType A type of the container for deserialized YAML values.
template <typename BasicNodeType>
class basic_deserializer {
static_assert(is_basic_node<BasicNodeType>::value, "basic_deserializer only accepts basic_node<...>");
/** A type for the target basic_node. */
using basic_node_type = BasicNodeType;
/** A type for the lexical analyzer. */
using lexer_type = lexical_analyzer;
/** A type for the document metainfo. */
using doc_metainfo_type = document_metainfo<basic_node_type>;
/** A type for the tag resolver. */
using tag_resolver_type = tag_resolver<basic_node_type>;
/** A type for the scalar parser. */
using scalar_parser_type = scalar_parser<basic_node_type>;
/** A type for sequence node value containers. */
using sequence_type = typename basic_node_type::sequence_type;
/** A type for mapping node value containers. */
using mapping_type = typename basic_node_type::mapping_type;
/// @brief Definition of state types of parse contexts.
enum class context_state_t : std::uint8_t {
BLOCK_MAPPING, //!< The underlying node is a block mapping.
BLOCK_MAPPING_EXPLICIT_KEY, //!< The underlying node is an explicit block mapping key.
BLOCK_MAPPING_EXPLICIT_VALUE, //!< The underlying node is an explicit block mapping value.
MAPPING_VALUE, //!< The underlying node is a block mapping value.
BLOCK_SEQUENCE, //!< The underlying node is a block sequence.
BLOCK_SEQUENCE_ENTRY, //!< The underlying node is a block sequence entry.
FLOW_SEQUENCE, //!< The underlying node is a flow sequence.
FLOW_SEQUENCE_KEY, //!< The underlying node is a flow sequence as a key.
FLOW_MAPPING, //!< The underlying node is a flow mapping.
FLOW_MAPPING_KEY, //!< The underlying node is a flow mapping as a key.
};
/// @brief Context information set for parsing.
struct parse_context {
/// @brief Construct a new parse_context object.
parse_context() = default;
/// @brief Construct a new parse_context object with non-default values for each parameter.
/// @param line The current line. (count from zero)
/// @param indent The indentation width in the current line. (count from zero)
/// @param state The parse context type.
/// @param p_node The underlying node associated to this context.
parse_context(uint32_t line, uint32_t indent, context_state_t state, basic_node_type* p_node) noexcept
: line(line),
indent(indent),
state(state),
p_node(p_node) {
}
parse_context(const parse_context&) noexcept = default;
parse_context& operator=(const parse_context&) noexcept = default;
parse_context(parse_context&&) noexcept = default;
parse_context& operator=(parse_context&&) noexcept = default;
~parse_context() {
switch (state) {
case context_state_t::BLOCK_MAPPING_EXPLICIT_KEY:
case context_state_t::FLOW_SEQUENCE_KEY:
case context_state_t::FLOW_MAPPING_KEY:
delete p_node;
p_node = nullptr;
break;
default:
break;
}
}
/// The current line. (count from zero)
uint32_t line {0};
/// The indentation width in the current line. (count from zero)
uint32_t indent {0};
/// The parse context type.
context_state_t state {context_state_t::BLOCK_MAPPING};
/// The pointer to the associated node to this context.
basic_node_type* p_node {nullptr};
};
/// @brief Definitions of state types for expected flow token hints.
enum class flow_token_state_t : std::uint8_t {
NEEDS_VALUE_OR_SUFFIX, //!< Either value or flow suffix (`]` or `}`)
NEEDS_SEPARATOR_OR_SUFFIX, //!< Either separator (`,`) or flow suffix (`]` or `}`)
};
public:
/// @brief Construct a new basic_deserializer object.
basic_deserializer() = default;
public:
/// @brief Deserialize a single YAML document into a YAML node.
/// @note
/// If the input consists of multiple YAML documents, this function only parses the first.
/// If the input may have multiple YAML documents all of which must be parsed into nodes,
/// prefer the `deserialize_docs()` function.
/// @tparam InputAdapterType The type of an input adapter object.
/// @param input_adapter An input adapter object for the input source buffer.
/// @return basic_node_type A root YAML node deserialized from the source string.
template <typename InputAdapterType, enable_if_t<is_input_adapter<InputAdapterType>::value, int> = 0>
basic_node_type deserialize(InputAdapterType&& input_adapter) { // NOLINT(cppcoreguidelines-missing-std-forward)
const str_view input_view = input_adapter.get_buffer_view();
lexer_type lexer(input_view);
lexical_token_t type {lexical_token_t::END_OF_BUFFER};
return deserialize_document(lexer, type);
}
/// @brief Deserialize multiple YAML documents into YAML nodes.
/// @tparam InputAdapterType The type of an adapter object.
/// @param input_adapter An input adapter object for the input source buffer.
/// @return std::vector<basic_node_type> Root YAML nodes for deserialized YAML documents.
template <typename InputAdapterType, enable_if_t<is_input_adapter<InputAdapterType>::value, int> = 0>
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
std::vector<basic_node_type> deserialize_docs(InputAdapterType&& input_adapter) {
const str_view input_view = input_adapter.get_buffer_view();
lexer_type lexer(input_view);
std::vector<basic_node_type> nodes {};
lexical_token_t type {lexical_token_t::END_OF_BUFFER};
do {
nodes.emplace_back(deserialize_document(lexer, type));
} while (type != lexical_token_t::END_OF_BUFFER);
return nodes;
} // LCOV_EXCL_LINE
private:
/// @brief Deserialize a YAML document into a YAML node.
/// @param lexer The lexical analyzer to be used.
/// @param last_type The variable to store the last lexical token type.
/// @return basic_node_type A root YAML node deserialized from the YAML document.
basic_node_type deserialize_document(lexer_type& lexer, lexical_token_t& last_type) {
lexical_token token {};
basic_node_type root;
mp_current_node = &root;
mp_meta = root.mp_meta;
// parse directives first.
deserialize_directives(lexer, token);
// parse node properties for root node if any
uint32_t line = lexer.get_lines_processed();
uint32_t indent = lexer.get_last_token_begin_pos();
const bool found_props = deserialize_node_properties(lexer, token, line, indent);
switch (token.type) {
case lexical_token_t::SEQUENCE_BLOCK_PREFIX: {
root = basic_node_type::sequence({basic_node_type()});
apply_directive_set(root);
if (found_props) {
// If node properties are found before the block sequence entry prefix, the properties belong to the
// root sequence node.
apply_node_properties(root);
}
parse_context context(
lexer.get_lines_processed(), lexer.get_last_token_begin_pos(), context_state_t::BLOCK_SEQUENCE, &root);
m_context_stack.emplace_back(context);
mp_current_node = &(root.as_seq().back());
apply_directive_set(*mp_current_node);
context.state = context_state_t::BLOCK_SEQUENCE_ENTRY;
context.p_node = mp_current_node;
m_context_stack.emplace_back(std::move(context));
token = lexer.get_next_token();
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
break;
}
case lexical_token_t::SEQUENCE_FLOW_BEGIN:
++m_flow_context_depth;
lexer.set_context_state(true);
root = basic_node_type::sequence();
apply_directive_set(root);
apply_node_properties(root);
m_context_stack.emplace_back(
lexer.get_lines_processed(), lexer.get_last_token_begin_pos(), context_state_t::FLOW_SEQUENCE, &root);
token = lexer.get_next_token();
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
break;
case lexical_token_t::MAPPING_FLOW_BEGIN:
++m_flow_context_depth;
lexer.set_context_state(true);
root = basic_node_type::mapping();
apply_directive_set(root);
apply_node_properties(root);
m_context_stack.emplace_back(
lexer.get_lines_processed(), lexer.get_last_token_begin_pos(), context_state_t::FLOW_MAPPING, &root);
token = lexer.get_next_token();
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
break;
case lexical_token_t::EXPLICIT_KEY_PREFIX: {
// If the explicit key prefix (? ) is detected here, the root node of current document must be a mapping.
// Also, tag and anchor if any are associated to the root mapping node.
// No get_next_token() call here to handle the token event in the deserialize_node() function.
root = basic_node_type::mapping();
apply_directive_set(root);
apply_node_properties(root);
parse_context context(
lexer.get_lines_processed(), lexer.get_last_token_begin_pos(), context_state_t::BLOCK_MAPPING, &root);
m_context_stack.emplace_back(std::move(context));
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
break;
}
case lexical_token_t::BLOCK_LITERAL_SCALAR:
case lexical_token_t::BLOCK_FOLDED_SCALAR:
// If a block scalar token is detected here, current document contains single scalar.
// Do nothing here since the token is handled in the deserialize_node() function.
break;
case lexical_token_t::PLAIN_SCALAR:
case lexical_token_t::SINGLE_QUOTED_SCALAR:
case lexical_token_t::DOUBLE_QUOTED_SCALAR:
case lexical_token_t::ALIAS_PREFIX:
// Defer handling the above token events until the next call on the deserialize_scalar() function since the
// meaning depends on subsequent events.
if (found_props && line < lexer.get_lines_processed()) {
// If node properties and a followed node are on the different line, the properties belong to the root
// node.
if (m_needs_anchor_impl) {
m_root_anchor_name = m_anchor_name;
m_needs_anchor_impl = false;
m_anchor_name = {};
}
if (m_needs_tag_impl) {
m_root_tag_name = m_tag_name;
m_needs_tag_impl = false;
m_tag_name = {};
}
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
}
break;
default:
// Do nothing since current document has no contents.
break;
}
// parse YAML nodes recursively
deserialize_node(lexer, token, line, indent, last_type);
FK_YAML_ASSERT(
last_type == lexical_token_t::END_OF_BUFFER || last_type == lexical_token_t::END_OF_DIRECTIVES ||
last_type == lexical_token_t::END_OF_DOCUMENT);
// reset parameters for the next call.
mp_current_node = nullptr;
mp_meta.reset();
m_needs_tag_impl = false;
m_needs_anchor_impl = false;
m_flow_context_depth = 0;
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
m_context_stack.clear();
return root;
}
/// @brief Deserializes the YAML directives if specified.
/// @param lexer The lexical analyzer to be used.
/// @param last_token Storage for last lexical token type.
void deserialize_directives(lexer_type& lexer, lexical_token& last_token) {
bool lacks_end_of_directives_marker = false;
lexer.set_document_state(true);
for (;;) {
const lexical_token token = lexer.get_next_token();
switch (token.type) {
case lexical_token_t::YAML_VER_DIRECTIVE:
if FK_YAML_UNLIKELY (mp_meta->is_version_specified) {
throw parse_error(
"YAML version cannot be specified more than once.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
mp_meta->version = convert_yaml_version(lexer.get_yaml_version());
mp_meta->is_version_specified = true;
lacks_end_of_directives_marker = true;
break;
case lexical_token_t::TAG_DIRECTIVE: {
const str_view tag_handle_view = lexer.get_tag_handle();
switch (tag_handle_view.size()) {
case 1 /* ! */: {
const bool is_already_specified = !mp_meta->primary_handle_prefix.empty();
if FK_YAML_UNLIKELY (is_already_specified) {
throw parse_error(
"Primary handle cannot be specified more than once.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
const str_view tag_prefix = lexer.get_tag_prefix();
mp_meta->primary_handle_prefix.assign(tag_prefix.begin(), tag_prefix.end());
lacks_end_of_directives_marker = true;
break;
}
case 2 /* !! */: {
const bool is_already_specified = !mp_meta->secondary_handle_prefix.empty();
if FK_YAML_UNLIKELY (is_already_specified) {
throw parse_error(
"Secondary handle cannot be specified more than once.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
const str_view tag_prefix = lexer.get_tag_prefix();
mp_meta->secondary_handle_prefix.assign(tag_prefix.begin(), tag_prefix.end());
lacks_end_of_directives_marker = true;
break;
}
default /* !<handle>! */: {
std::string tag_handle(tag_handle_view.begin(), tag_handle_view.end());
const str_view tag_prefix_view = lexer.get_tag_prefix();
std::string tag_prefix(tag_prefix_view.begin(), tag_prefix_view.end());
const bool is_already_specified =
!(mp_meta->named_handle_map.emplace(std::move(tag_handle), std::move(tag_prefix)).second);
if FK_YAML_UNLIKELY (is_already_specified) {
throw parse_error(
"The same named handle cannot be specified more than once.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
lacks_end_of_directives_marker = true;
break;
}
}
break;
}
case lexical_token_t::INVALID_DIRECTIVE:
// TODO: should output a warning log. Currently just ignore this case.
break;
case lexical_token_t::END_OF_DIRECTIVES:
lacks_end_of_directives_marker = false;
break;
default:
if FK_YAML_UNLIKELY (lacks_end_of_directives_marker) {
throw parse_error(
"The end of directives marker (---) is missing after directives.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
// end the parsing of directives if the other tokens are found.
last_token = token;
lexer.set_document_state(false);
return;
}
}
}
/// @brief Deserializes the YAML nodes recursively.
/// @param lexer The lexical analyzer to be used.
/// @param first_type The first lexical token.
/// @param last_type Storage for last lexical token type.
void deserialize_node(
lexer_type& lexer, const lexical_token& first_token, uint32_t first_line, uint32_t first_indent,
lexical_token_t& last_type) {
lexical_token token = first_token;
uint32_t line = first_line;
uint32_t indent = first_indent;
do {
switch (token.type) {
case lexical_token_t::EXPLICIT_KEY_PREFIX: {
const bool needs_to_move_back = indent == 0 || indent < m_context_stack.back().indent;
if (needs_to_move_back) {
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return c.state == context_state_t::BLOCK_MAPPING && indent == c.indent;
});
}
switch (m_context_stack.back().state) {
case context_state_t::MAPPING_VALUE:
case context_state_t::BLOCK_MAPPING_EXPLICIT_KEY:
case context_state_t::BLOCK_MAPPING_EXPLICIT_VALUE:
case context_state_t::BLOCK_SEQUENCE_ENTRY:
// This path is needed in case the input contains nested explicit keys.
// ```yaml
// foo:
// ? ? foo
// : bar
// : ? baz
// : - ? qux
// : 123
// ```
*mp_current_node = basic_node_type::mapping();
apply_directive_set(*mp_current_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
break;
default:
break;
}
token = lexer.get_next_token();
if (token.type == lexical_token_t::SEQUENCE_BLOCK_PREFIX) {
// heap-allocated node will be freed in handling the corresponding KEY_SEPARATOR event
auto* p_node = new basic_node_type(node_type::SEQUENCE);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING_EXPLICIT_KEY, p_node);
apply_directive_set(*p_node);
parse_context context(
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos(),
context_state_t::BLOCK_SEQUENCE,
p_node);
m_context_stack.emplace_back(context);
p_node->as_seq().emplace_back(basic_node_type());
mp_current_node = &(p_node->as_seq().back());
apply_directive_set(*mp_current_node);
context.state = context_state_t::BLOCK_SEQUENCE_ENTRY;
context.p_node = mp_current_node;
m_context_stack.emplace_back(std::move(context));
break;
}
// heap-allocated node will be freed in handling the corresponding KEY_SEPARATOR event
m_context_stack.emplace_back(
line, indent, context_state_t::BLOCK_MAPPING_EXPLICIT_KEY, new basic_node_type());
mp_current_node = m_context_stack.back().p_node;
apply_directive_set(*mp_current_node);
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
continue;
}
case lexical_token_t::KEY_SEPARATOR: {
FK_YAML_ASSERT(!m_context_stack.empty());
if FK_YAML_UNLIKELY (m_context_stack.back().state == context_state_t::BLOCK_SEQUENCE_ENTRY) {
// empty mapping keys are not supported.
// ```yaml
// - : foo
// ```
throw parse_error("sequence key should not be empty.", line, indent);
}
if (m_flow_context_depth > 0) {
break;
}
// hold the line count of the key separator for later use.
const uint32_t old_indent = indent;
const uint32_t old_line = line;
token = lexer.get_next_token();
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
const bool found_props = deserialize_node_properties(lexer, token, line, indent);
if (found_props && line == lexer.get_lines_processed()) {
// defer applying node properties for the subsequent node on the same line.
continue;
}
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
const bool is_implicit_same_line =
(line == old_line) && (m_context_stack.empty() || old_indent > m_context_stack.back().indent);
if (is_implicit_same_line) {
// a key separator for an implicit key with its value on the same line.
continue;
}
if (line > old_line) {
if (m_needs_tag_impl) {
const tag_t tag_type = tag_resolver_type::resolve_tag(m_tag_name, mp_meta);
if (tag_type == tag_t::MAPPING || tag_type == tag_t::CUSTOM_TAG) {
// set YAML node properties here to distinguish them from those for the first key node
// as shown in the following snippet:
//
// ```yaml
// foo: !!map
// !!str 123: true
// ^
// this !!str tag overwrites the preceding !!map tag.
// ```
*mp_current_node = basic_node_type::mapping();
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
continue;
}
}
if (token.type == lexical_token_t::SEQUENCE_BLOCK_PREFIX) {
// a key separator preceding block sequence entries
*mp_current_node = basic_node_type::sequence({basic_node_type()});
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
auto& cur_context = m_context_stack.back();
cur_context.line = line;
cur_context.indent = indent;
cur_context.state = context_state_t::BLOCK_SEQUENCE;
mp_current_node = &(mp_current_node->as_seq().back());
apply_directive_set(*mp_current_node);
parse_context entry_context = cur_context;
entry_context.state = context_state_t::BLOCK_SEQUENCE_ENTRY;
entry_context.p_node = mp_current_node;
m_context_stack.emplace_back(std::move(entry_context));
token = lexer.get_next_token();
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
const bool has_props = deserialize_node_properties(lexer, token, line, indent);
if (has_props) {
const uint32_t line_after_props = lexer.get_lines_processed();
if (line == line_after_props) {
// Skip updating the current indent to avoid stacking a wrong indentation.
//
// ```yaml
// &foo bar: baz
// ^
// the correct indent width for the "bar" node key.
// ```
continue;
}
// if node properties and the followed node are on different lines (i.e., the properties are
// for a container node), the application and the line advancement must happen here.
// Otherwise, a false indent error will be emitted. See
// https://github.com/fktn-k/fkYAML/issues/368 for more details.
line = line_after_props;
indent = lexer.get_last_token_begin_pos();
*mp_current_node = basic_node_type::mapping();
m_context_stack.emplace_back(
line_after_props, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
}
continue;
}
if (indent <= m_context_stack.back().indent) {
FK_YAML_ASSERT(m_context_stack.back().state == context_state_t::MAPPING_VALUE);
// Mapping values can be omitted and are considered to be null.
// ```yaml
// foo:
// bar:
// baz:
// qux:
// # -> {foo: null, bar: {baz: null}, qux: null}
// ```
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return (c.state == context_state_t::BLOCK_MAPPING) && (indent == c.indent);
});
}
// defer checking the existence of a key separator after the following scalar until the next
// deserialize_scalar() call.
continue;
}
// handle explicit mapping key separators.
FK_YAML_ASSERT(m_context_stack.back().state == context_state_t::BLOCK_MAPPING_EXPLICIT_KEY);
basic_node_type key_node = std::move(*m_context_stack.back().p_node);
m_context_stack.pop_back();
m_context_stack.back().p_node->as_map().emplace(key_node, basic_node_type());
mp_current_node = &(m_context_stack.back().p_node->operator[](std::move(key_node)));
m_context_stack.emplace_back(
old_line, old_indent, context_state_t::BLOCK_MAPPING_EXPLICIT_VALUE, mp_current_node);
if (token.type == lexical_token_t::SEQUENCE_BLOCK_PREFIX) {
*mp_current_node = basic_node_type::sequence({basic_node_type()});
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_SEQUENCE, mp_current_node);
mp_current_node = &(mp_current_node->as_seq().back());
parse_context entry_context = m_context_stack.back();
entry_context.state = context_state_t::BLOCK_SEQUENCE_ENTRY;
entry_context.p_node = mp_current_node;
m_context_stack.emplace_back(std::move(entry_context));
break;
}
continue;
}
case lexical_token_t::ANCHOR_PREFIX:
case lexical_token_t::TAG_PREFIX:
deserialize_node_properties(lexer, token, line, indent);
// Skip updating the current indent to avoid stacking a wrong indentation.
// Note that node properties for block sequences as a mapping value are processed when a
// `lexical_token_t::KEY_SEPARATOR` token is processed.
//
// ```yaml
// &foo bar: baz
// ^
// the correct indent width for the "bar" node key.
// ```
continue;
case lexical_token_t::SEQUENCE_BLOCK_PREFIX: {
FK_YAML_ASSERT(!m_context_stack.empty());
const uint32_t parent_indent = m_context_stack.back().indent;
if (indent == parent_indent) {
// If the previous block sequence entry is empty, just move to the parent context.
// ```yaml
// foo:
// -
// - bar
// # ^ (here)
// # -> {foo: [null, bar]}
// ```
pop_to_parent_node(line, indent, [](const parse_context& c) {
return c.state == context_state_t::BLOCK_SEQUENCE;
});
}
else if (indent < parent_indent) {
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return c.state == context_state_t::BLOCK_SEQUENCE && indent == c.indent;
});
}
else /*parent_indent < indent*/ {
if FK_YAML_UNLIKELY (m_context_stack.back().state == context_state_t::BLOCK_SEQUENCE) {
// bad indentation like the following YAML:
// ```yaml
// - "foo"
// - bar
// # ^
// ```
throw parse_error("bad indentation of a mapping entry.", line, indent);
}
*mp_current_node = basic_node_type::sequence();
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_SEQUENCE, mp_current_node);
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
}
auto& seq = mp_current_node->as_seq();
seq.emplace_back(basic_node_type());
mp_current_node = &(seq.back());
apply_directive_set(*mp_current_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_SEQUENCE_ENTRY, mp_current_node);
break;
}
case lexical_token_t::SEQUENCE_FLOW_BEGIN:
if (m_flow_context_depth == 0) {
lexer.set_context_state(true);
if (indent <= m_context_stack.back().indent) {
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
switch (c.state) {
case context_state_t::BLOCK_MAPPING:
case context_state_t::MAPPING_VALUE:
return indent == c.indent;
default:
return false;
}
});
}
}
else if FK_YAML_UNLIKELY (m_flow_token_state == flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX) {
throw parse_error("Flow sequence beginning is found without separated with a comma.", line, indent);
}
++m_flow_context_depth;
switch (m_context_stack.back().state) {
case context_state_t::BLOCK_SEQUENCE:
case context_state_t::FLOW_SEQUENCE:
mp_current_node->as_seq().emplace_back(basic_node_type::sequence());
mp_current_node = &(mp_current_node->as_seq().back());
m_context_stack.emplace_back(line, indent, context_state_t::FLOW_SEQUENCE, mp_current_node);
break;
case context_state_t::BLOCK_MAPPING:
case context_state_t::FLOW_MAPPING:
// heap-allocated node will be freed in handling the corresponding SEQUENCE_FLOW_END event.
m_context_stack.emplace_back(
line, indent, context_state_t::FLOW_SEQUENCE_KEY, new basic_node_type(node_type::SEQUENCE));
mp_current_node = m_context_stack.back().p_node;
break;
default: {
*mp_current_node = basic_node_type::sequence();
parse_context& last_context = m_context_stack.back();
last_context.line = line;
last_context.indent = indent;
last_context.state = context_state_t::FLOW_SEQUENCE;
break;
}
}
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
break;
case lexical_token_t::SEQUENCE_FLOW_END: {
if FK_YAML_UNLIKELY (m_flow_context_depth == 0) {
throw parse_error("Flow sequence ending is found outside the flow context.", line, indent);
}
if (--m_flow_context_depth == 0) {
lexer.set_context_state(false);
}
// find the corresponding flow sequence beginning.
auto itr = std::find_if( // LCOV_EXCL_LINE
m_context_stack.rbegin(),
m_context_stack.rend(),
[](const parse_context& c) {
switch (c.state) {
case context_state_t::FLOW_SEQUENCE_KEY:
case context_state_t::FLOW_SEQUENCE:
return true;
default:
return false;
}
});
const bool is_valid = itr != m_context_stack.rend();
if FK_YAML_UNLIKELY (!is_valid) {
throw parse_error("No corresponding flow sequence beginning is found.", line, indent);
}
// keep the last state for later processing.
parse_context& last_context = m_context_stack.back();
mp_current_node = last_context.p_node;
last_context.p_node = nullptr;
indent = last_context.indent;
const context_state_t state = last_context.state;
m_context_stack.pop_back();
// handle cases where the flow sequence is a mapping key node.
if (!m_context_stack.empty() && state == context_state_t::FLOW_SEQUENCE_KEY) {
basic_node_type key_node = std::move(*mp_current_node);
delete mp_current_node;
mp_current_node = m_context_stack.back().p_node;
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
add_new_key(std::move(key_node), line, indent);
break;
}
token = lexer.get_next_token();
if (token.type == lexical_token_t::KEY_SEPARATOR) {
basic_node_type key_node = basic_node_type::mapping();
apply_directive_set(key_node);
mp_current_node->swap(key_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
add_new_key(std::move(key_node), line, indent);
}
else {
if (!m_context_stack.empty()) {
mp_current_node = m_context_stack.back().p_node;
}
if (m_flow_context_depth > 0) {
m_flow_token_state = flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX;
}
}
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
continue;
}
case lexical_token_t::MAPPING_FLOW_BEGIN:
if (m_flow_context_depth == 0) {
lexer.set_context_state(true);
if (indent <= m_context_stack.back().indent) {
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
switch (c.state) {
case context_state_t::BLOCK_MAPPING:
case context_state_t::MAPPING_VALUE:
return indent == c.indent;
default:
return false;
}
});
}
}
else if FK_YAML_UNLIKELY (m_flow_token_state == flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX) {
throw parse_error("Flow mapping beginning is found without separated with a comma.", line, indent);
}
++m_flow_context_depth;
switch (m_context_stack.back().state) {
case context_state_t::BLOCK_SEQUENCE:
case context_state_t::FLOW_SEQUENCE:
mp_current_node->as_seq().emplace_back(basic_node_type::mapping());
mp_current_node = &(mp_current_node->as_seq().back());
m_context_stack.emplace_back(line, indent, context_state_t::FLOW_MAPPING, mp_current_node);
break;
case context_state_t::BLOCK_MAPPING:
case context_state_t::FLOW_MAPPING:
// heap-allocated node will be freed in handling the corresponding MAPPING_FLOW_END event.
m_context_stack.emplace_back(
line, indent, context_state_t::FLOW_MAPPING_KEY, new basic_node_type(node_type::MAPPING));
mp_current_node = m_context_stack.back().p_node;
break;
default: {
*mp_current_node = basic_node_type::mapping();
parse_context& last_context = m_context_stack.back();
last_context.line = line;
last_context.indent = indent;
last_context.state = context_state_t::FLOW_MAPPING;
break;
}
}
apply_directive_set(*mp_current_node);
apply_node_properties(*mp_current_node);
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
break;
case lexical_token_t::MAPPING_FLOW_END: {
if FK_YAML_UNLIKELY (m_flow_context_depth == 0) {
throw parse_error("Flow mapping ending is found outside the flow context.", line, indent);
}
if (--m_flow_context_depth == 0) {
lexer.set_context_state(false);
}
// find the corresponding flow mapping beginning.
auto itr = std::find_if( // LCOV_EXCL_LINE
m_context_stack.rbegin(),
m_context_stack.rend(),
[](const parse_context& c) {
switch (c.state) {
case context_state_t::FLOW_MAPPING_KEY:
case context_state_t::FLOW_MAPPING:
return true;
default:
return false;
}
});
const bool is_valid = itr != m_context_stack.rend();
if FK_YAML_UNLIKELY (!is_valid) {
throw parse_error("No corresponding flow mapping beginning is found.", line, indent);
}
// keep the last state for later processing.
parse_context& last_context = m_context_stack.back();
mp_current_node = last_context.p_node;
last_context.p_node = nullptr;
indent = last_context.indent;
const context_state_t state = last_context.state;
m_context_stack.pop_back();
// handle cases where the flow mapping is a mapping key node.
if (!m_context_stack.empty() && state == context_state_t::FLOW_MAPPING_KEY) {
basic_node_type key_node = std::move(*mp_current_node);
delete mp_current_node;
mp_current_node = m_context_stack.back().p_node;
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
add_new_key(std::move(key_node), line, indent);
break;
}
token = lexer.get_next_token();
if (token.type == lexical_token_t::KEY_SEPARATOR) {
basic_node_type key_node = basic_node_type::mapping();
apply_directive_set(key_node);
mp_current_node->swap(key_node);
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
add_new_key(std::move(key_node), line, indent);
}
else {
if (!m_context_stack.empty()) {
mp_current_node = m_context_stack.back().p_node;
}
if (m_flow_context_depth > 0) {
m_flow_token_state = flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX;
}
}
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
continue;
}
case lexical_token_t::VALUE_SEPARATOR:
FK_YAML_ASSERT(m_flow_context_depth > 0);
if FK_YAML_UNLIKELY (m_flow_token_state != flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX) {
throw parse_error("invalid value separator is found.", line, indent);
}
m_flow_token_state = flow_token_state_t::NEEDS_VALUE_OR_SUFFIX;
break;
case lexical_token_t::ALIAS_PREFIX: {
// An alias node must not specify any properties (tag, anchor).
// https://yaml.org/spec/1.2.2/#71-alias-nodes
if FK_YAML_UNLIKELY (m_needs_tag_impl) {
throw parse_error("Tag cannot be specified to an alias node", line, indent);
}
if FK_YAML_UNLIKELY (m_needs_anchor_impl) {
throw parse_error("Anchor cannot be specified to an alias node.", line, indent);
}
std::string token_str = std::string(token.str.begin(), token.str.end());
const auto anchor_counts = static_cast<uint32_t>(mp_meta->anchor_table.count(token_str));
if FK_YAML_UNLIKELY (anchor_counts == 0) {
throw parse_error("The given anchor name must appear prior to the alias node.", line, indent);
}
basic_node_type node {};
node.m_attrs |= detail::node_attr_bits::alias_bit;
node.m_prop.anchor = std::move(token_str);
detail::node_attr_bits::set_anchor_offset(anchor_counts - 1, node.m_attrs);
apply_directive_set(node);
apply_node_properties(node);
deserialize_scalar(lexer, std::move(node), indent, line, token);
continue;
}
case lexical_token_t::PLAIN_SCALAR:
case lexical_token_t::SINGLE_QUOTED_SCALAR:
case lexical_token_t::DOUBLE_QUOTED_SCALAR: {
tag_t tag_type {tag_t::NONE};
if (m_needs_tag_impl) {
tag_type = tag_resolver_type::resolve_tag(m_tag_name, mp_meta);
}
basic_node_type node = scalar_parser_type(line, indent).parse_flow(token.type, tag_type, token.str);
apply_directive_set(node);
apply_node_properties(node);
deserialize_scalar(lexer, std::move(node), indent, line, token);
continue;
}
case lexical_token_t::BLOCK_LITERAL_SCALAR:
case lexical_token_t::BLOCK_FOLDED_SCALAR: {
tag_t tag_type {tag_t::NONE};
if (m_needs_tag_impl) {
tag_type = tag_resolver_type::resolve_tag(m_tag_name, mp_meta);
}
basic_node_type node =
scalar_parser_type(line, indent)
.parse_block(token.type, tag_type, token.str, lexer.get_block_scalar_header());
apply_directive_set(node);
apply_node_properties(node);
deserialize_scalar(lexer, std::move(node), indent, line, token);
continue;
}
// these tokens end parsing the current YAML document.
case lexical_token_t::END_OF_BUFFER:
// This handles an empty input.
last_type = token.type;
return;
case lexical_token_t::END_OF_DIRECTIVES:
case lexical_token_t::END_OF_DOCUMENT:
if FK_YAML_UNLIKELY (m_flow_context_depth > 0) {
throw parse_error("An invalid document marker found in a flow collection", line, indent);
}
last_type = token.type;
return;
// no way to come here while lexically analyzing document contents.
case lexical_token_t::YAML_VER_DIRECTIVE: // LCOV_EXCL_LINE
case lexical_token_t::TAG_DIRECTIVE: // LCOV_EXCL_LINE
case lexical_token_t::INVALID_DIRECTIVE: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
token = lexer.get_next_token();
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
} while (token.type != lexical_token_t::END_OF_BUFFER);
last_type = token.type;
}
/// @brief Deserializes YAML node properties (anchor and/or tag names) if they exist
/// @param lexer The lexical analyzer to be used.
/// @param last_type The variable to store the last lexical token type.
/// @param line The variable to store the line of either the first property or the last non-property token.
/// @param indent The variable to store the indent of either the first property or the last non-property token.
/// @return true if any property is found, false otherwise.
bool deserialize_node_properties(lexer_type& lexer, lexical_token& last_token, uint32_t& line, uint32_t& indent) {
m_needs_anchor_impl = m_needs_tag_impl = false;
lexical_token token = last_token;
bool ends_loop {false};
do {
if (line < lexer.get_lines_processed()) {
break;
}
switch (token.type) {
case lexical_token_t::ANCHOR_PREFIX:
if FK_YAML_UNLIKELY (m_needs_anchor_impl) {
throw parse_error(
"anchor name cannot be specified more than once to the same node.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
m_anchor_name = token.str;
m_needs_anchor_impl = true;
if (!m_needs_tag_impl) {
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
}
token = lexer.get_next_token();
break;
case lexical_token_t::TAG_PREFIX: {
if FK_YAML_UNLIKELY (m_needs_tag_impl) {
throw parse_error(
"tag name cannot be specified more than once to the same node.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
m_tag_name = token.str;
m_needs_tag_impl = true;
if (!m_needs_anchor_impl) {
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
}
token = lexer.get_next_token();
break;
}
default:
ends_loop = true;
break;
}
} while (!ends_loop);
last_token = token;
const bool prop_specified = m_needs_anchor_impl || m_needs_tag_impl;
if (!prop_specified) {
line = lexer.get_lines_processed();
indent = lexer.get_last_token_begin_pos();
}
return prop_specified;
}
/// @brief Add new key string to the current YAML node.
/// @param key a key string to be added to the current YAML node.
/// @param line The line where the key is found.
/// @param indent The indentation width in the current line where the key is found.
void add_new_key(basic_node_type&& key, const uint32_t line, const uint32_t indent) {
if (m_flow_context_depth == 0) {
if FK_YAML_UNLIKELY (m_context_stack.back().indent < indent) {
// bad indentation like the following YAML:
// ```yaml
// foo: true
// baz: 123
// # ^
// ```
throw parse_error("bad indentation of a mapping entry.", line, indent);
}
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return (c.state == context_state_t::BLOCK_MAPPING) && (indent == c.indent);
});
}
else {
if FK_YAML_UNLIKELY (m_flow_token_state != flow_token_state_t::NEEDS_VALUE_OR_SUFFIX) {
throw parse_error("Flow mapping entry is found without separated with a comma.", line, indent);
}
if (mp_current_node->is_sequence()) {
mp_current_node->as_seq().emplace_back(basic_node_type::mapping());
mp_current_node = &(mp_current_node->operator[](mp_current_node->size() - 1));
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
}
}
auto itr = mp_current_node->as_map().emplace(std::move(key), basic_node_type());
if FK_YAML_UNLIKELY (!itr.second) {
throw parse_error("Detected duplication in mapping keys.", line, indent);
}
mp_current_node = &(itr.first->second);
const parse_context& key_context = m_context_stack.back();
m_context_stack.emplace_back(
key_context.line, key_context.indent, context_state_t::MAPPING_VALUE, mp_current_node);
}
/// @brief Assign node value to the current node.
/// @param node_value A rvalue basic_node_type object to be assigned to the current node.
void assign_node_value(basic_node_type&& node_value, const uint32_t line, const uint32_t indent) {
if (mp_current_node->is_sequence()) {
FK_YAML_ASSERT(m_flow_context_depth > 0);
if FK_YAML_UNLIKELY (m_flow_token_state != flow_token_state_t::NEEDS_VALUE_OR_SUFFIX) {
// Flow sequence entries are not allowed to be empty.
// ```yaml
// [foo,,bar]
// ```
throw parse_error("flow sequence entry is found without separated with a comma.", line, indent);
}
mp_current_node->as_seq().emplace_back(std::move(node_value));
m_flow_token_state = flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX;
return;
}
// a scalar node
*mp_current_node = std::move(node_value);
if FK_YAML_UNLIKELY (m_context_stack.empty()) {
// single scalar document.
return;
}
if FK_YAML_LIKELY (m_context_stack.back().state != context_state_t::BLOCK_MAPPING_EXPLICIT_KEY) {
m_context_stack.pop_back();
mp_current_node = m_context_stack.back().p_node;
if (m_flow_context_depth > 0) {
m_flow_token_state = flow_token_state_t::NEEDS_SEPARATOR_OR_SUFFIX;
}
}
}
/// @brief Deserialize a detected scalar node.
/// @param lexer The lexical analyzer to be used.
/// @param node A scalar node.
/// @param indent The current indentation width. Can be updated in this function.
/// @param line The number of processed lines. Can be updated in this function.
/// @param token The storage for last lexical token.
/// @return true if next token has already been got, false otherwise.
void deserialize_scalar(
lexer_type& lexer, basic_node_type&& node, uint32_t& indent, uint32_t& line, lexical_token& token) {
token = lexer.get_next_token();
if (mp_current_node->is_mapping()) {
const bool is_key_sep_followed =
(token.type == lexical_token_t::KEY_SEPARATOR) && (line == lexer.get_lines_processed());
if FK_YAML_UNLIKELY (!is_key_sep_followed) {
throw parse_error(
"The \":\" mapping value indicator must be followed after a mapping key.",
lexer.get_lines_processed(),
lexer.get_last_token_begin_pos());
}
add_new_key(std::move(node), line, indent);
}
else if (token.type == lexical_token_t::KEY_SEPARATOR) {
if FK_YAML_UNLIKELY (line != lexer.get_lines_processed()) {
// This path is for explicit mapping key separator like:
// ```yaml
// ? foo
// : bar
// # ^ this separator
// ```
assign_node_value(std::move(node), line, indent);
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
if (m_context_stack.back().state != context_state_t::BLOCK_MAPPING_EXPLICIT_KEY) {
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return c.state == context_state_t::BLOCK_MAPPING_EXPLICIT_KEY && indent == c.indent;
});
}
return;
}
if (mp_current_node->is_scalar()) {
if FK_YAML_LIKELY (!m_context_stack.empty()) {
parse_context& cur_context = m_context_stack.back();
switch (cur_context.state) {
case context_state_t::BLOCK_MAPPING_EXPLICIT_KEY:
case context_state_t::BLOCK_MAPPING_EXPLICIT_VALUE:
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
break;
case context_state_t::BLOCK_SEQUENCE_ENTRY:
if FK_YAML_UNLIKELY (cur_context.indent >= indent) {
// This handles combination of empty block sequence entry and block mapping entry with the
// same indentation level, for examples:
// ```yaml
// foo:
// bar:
// - # These entries are indented
// baz: 123 # with the same width.
// # ^^^
// ```
pop_to_parent_node(line, indent, [indent](const parse_context& c) {
return c.state == context_state_t::BLOCK_MAPPING && indent == c.indent;
});
add_new_key(std::move(node), line, indent);
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
return;
}
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
break;
default:
if FK_YAML_UNLIKELY (cur_context.line == line) {
throw parse_error("Multiple mapping keys are specified on the same line.", line, indent);
}
cur_context.line = line;
cur_context.indent = indent;
cur_context.state = context_state_t::BLOCK_MAPPING;
break;
}
*mp_current_node = basic_node_type::mapping();
apply_directive_set(*mp_current_node);
}
else {
// root mapping node
m_context_stack.emplace_back(line, indent, context_state_t::BLOCK_MAPPING, mp_current_node);
*mp_current_node = basic_node_type::mapping();
apply_directive_set(*mp_current_node);
// apply node properties if any to the root mapping node.
if (!m_root_anchor_name.empty()) {
mp_current_node->add_anchor_name(
std::string(m_root_anchor_name.begin(), m_root_anchor_name.end()));
m_root_anchor_name = {};
}
if (!m_root_tag_name.empty()) {
mp_current_node->add_tag_name(std::string(m_root_tag_name.begin(), m_root_tag_name.end()));
m_root_tag_name = {};
}
}
}
add_new_key(std::move(node), line, indent);
}
else {
assign_node_value(std::move(node), line, indent);
}
indent = lexer.get_last_token_begin_pos();
line = lexer.get_lines_processed();
}
/// @brief Pops parent contexts to a block mapping with the given indentation.
/// @tparam Pred Functor type to test parent contexts.
/// @param line The current line count.
/// @param indent The indentation level of the target parent block mapping.
template <typename Pred>
void pop_to_parent_node(uint32_t line, uint32_t indent, Pred&& pred) {
FK_YAML_ASSERT(!m_context_stack.empty());
// LCOV_EXCL_START
auto itr = std::find_if(m_context_stack.rbegin(), m_context_stack.rend(), std::forward<Pred>(pred));
// LCOV_EXCL_STOP
const bool is_indent_valid = (itr != m_context_stack.rend());
if FK_YAML_UNLIKELY (!is_indent_valid) {
throw parse_error("Detected invalid indentation.", line, indent);
}
const auto pop_num = static_cast<uint32_t>(std::distance(m_context_stack.rbegin(), itr));
// move back to the parent block mapping.
for (uint32_t i = 0; i < pop_num; i++) {
m_context_stack.pop_back();
}
mp_current_node = m_context_stack.back().p_node;
}
/// @brief Set YAML directive properties to the given node.
/// @param node A basic_node_type object to be set YAML directive properties.
void apply_directive_set(basic_node_type& node) noexcept {
node.mp_meta = mp_meta;
}
/// @brief Set YAML node properties (anchor and/or tag names) to the given node.
/// @param node A node type object to be set YAML node properties.
void apply_node_properties(basic_node_type& node) {
if (m_needs_anchor_impl) {
node.add_anchor_name(std::string(m_anchor_name.begin(), m_anchor_name.end()));
m_needs_anchor_impl = false;
m_anchor_name = {};
}
if (m_needs_tag_impl) {
node.add_tag_name(std::string(m_tag_name.begin(), m_tag_name.end()));
m_needs_tag_impl = false;
m_tag_name = {};
}
}
/// @brief Update the target YAML version with an input string.
/// @param version_str A YAML version string.
yaml_version_type convert_yaml_version(str_view version_str) noexcept {
return (version_str.compare("1.1") == 0) ? yaml_version_type::VERSION_1_1 : yaml_version_type::VERSION_1_2;
}
private:
/// The currently focused YAML node.
basic_node_type* mp_current_node {nullptr};
/// The stack of parse contexts.
std::deque<parse_context> m_context_stack {};
/// The current depth of flow contexts.
uint32_t m_flow_context_depth {0};
/// The set of YAML directives.
std::shared_ptr<doc_metainfo_type> mp_meta {};
/// A flag to determine the need for YAML anchor node implementation.
bool m_needs_anchor_impl {false};
/// A flag to determine the need for a corresponding node with the last YAML tag.
bool m_needs_tag_impl {false};
/// A flag to determine the need for a value separator or a flow suffix to follow.
flow_token_state_t m_flow_token_state {flow_token_state_t::NEEDS_VALUE_OR_SUFFIX};
/// The last YAML anchor name.
str_view m_anchor_name;
/// The last tag name.
str_view m_tag_name;
/// The root YAML anchor name. (maybe empty and unused)
str_view m_root_anchor_name;
/// The root tag name. (maybe empty and unused)
str_view m_root_tag_name;
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_INPUT_DESERIALIZER_HPP */
// #include <fkYAML/detail/input/input_adapter.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP
#define FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP
#include <array>
#include <cstdio>
#include <cstring>
#include <deque>
#include <istream>
#include <iterator>
#include <string>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/assert.hpp>
// #include <fkYAML/detail/encodings/utf_encode_detector.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_DETECTOR_HPP
#define FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_DETECTOR_HPP
#include <cstdint>
#include <istream>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/encodings/utf_encode_t.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_T_HPP
#define FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_T_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Definition of Unicode encoding types
/// @note Since fkYAML doesn't treat UTF-16/UTF-32 encoded characters per byte, endians do not matter.
enum class utf_encode_t : std::uint8_t {
UTF_8, //!< UTF-8
UTF_16BE, //!< UTF-16 Big Endian
UTF_16LE, //!< UTF-16 Little Endian
UTF_32BE, //!< UTF-32 Big Endian
UTF_32LE, //!< UTF-32 Little Endian
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_T_HPP */
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief Detect an encoding type for UTF-8 expected inputs.
/// @note This function doesn't support the case where the first character is null.
/// @param[in] bytes 4 bytes of an input character sequence.
/// @param[out] has_bom Whether the input contains a BOM.
/// @return A detected encoding type.
inline utf_encode_t detect_encoding_type(const std::array<uint8_t, 4>& bytes, bool& has_bom) noexcept {
has_bom = false;
const uint8_t byte0 = bytes[0];
const uint8_t byte1 = bytes[1];
const uint8_t byte2 = bytes[2];
const uint8_t byte3 = bytes[3];
// Check if a BOM exists.
if (byte0 == static_cast<uint8_t>(0xEFu) && byte1 == static_cast<uint8_t>(0xBBu) &&
byte2 == static_cast<uint8_t>(0xBFu)) {
has_bom = true;
return utf_encode_t::UTF_8;
}
if (byte0 == 0 && byte1 == 0 && byte2 == static_cast<uint8_t>(0xFEu) && byte3 == static_cast<uint8_t>(0xFFu)) {
has_bom = true;
return utf_encode_t::UTF_32BE;
}
if (byte0 == static_cast<uint8_t>(0xFFu) && byte1 == static_cast<uint8_t>(0xFEu) && byte2 == 0 && byte3 == 0) {
has_bom = true;
return utf_encode_t::UTF_32LE;
}
if (byte0 == static_cast<uint8_t>(0xFEu) && byte1 == static_cast<uint8_t>(0xFFu)) {
has_bom = true;
return utf_encode_t::UTF_16BE;
}
if (byte0 == static_cast<uint8_t>(0xFFu) && byte1 == static_cast<uint8_t>(0xFEu)) {
has_bom = true;
return utf_encode_t::UTF_16LE;
}
// Test the first character assuming it's an ASCII character.
if (byte0 == 0 && byte1 == 0 && byte2 == 0 && 0 < byte3 && byte3 < static_cast<uint8_t>(0x80u)) {
return utf_encode_t::UTF_32BE;
}
if (0 < byte0 && byte0 < static_cast<uint8_t>(0x80u) && byte1 == 0 && byte2 == 0 && byte3 == 0) {
return utf_encode_t::UTF_32LE;
}
if (byte0 == 0 && 0 < byte1 && byte1 < static_cast<uint8_t>(0x80u)) {
return utf_encode_t::UTF_16BE;
}
if (0 < byte0 && byte0 < static_cast<uint8_t>(0x80u) && byte1 == 0) {
return utf_encode_t::UTF_16LE;
}
return utf_encode_t::UTF_8;
}
/// @brief A class which detects UTF encoding type and the existence of a BOM at the beginning.
/// @tparam ItrType Type of iterators for the input.
template <typename ItrType, typename = void>
struct utf_encode_detector {};
/// @brief The partial specialization of utf_encode_detector for char iterators.
/// @tparam ItrType An iterator type.
template <typename ItrType>
struct utf_encode_detector<ItrType, enable_if_t<is_iterator_of<ItrType, char>::value>> {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param begin The iterator to the first element of an input.
/// @param end The iterator to the past-the end element of an input.
/// @return A detected encoding type.
static utf_encode_t detect(ItrType& begin, const ItrType& end) noexcept {
if FK_YAML_UNLIKELY (begin == end) {
return utf_encode_t::UTF_8;
}
// the inner curly braces are necessary for older compilers
std::array<uint8_t, 4> bytes {{}};
bytes.fill(0xFFu);
auto current = begin;
for (int i = 0; i < 4 && current != end; i++, ++current) {
bytes[i] = static_cast<uint8_t>(*current); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
if (has_bom) {
// skip reading the BOM.
switch (encode_type) {
case utf_encode_t::UTF_8:
std::advance(begin, 3);
break;
case utf_encode_t::UTF_16BE:
case utf_encode_t::UTF_16LE:
std::advance(begin, 2);
break;
case utf_encode_t::UTF_32BE:
case utf_encode_t::UTF_32LE:
std::advance(begin, 4);
break;
}
}
return encode_type;
}
};
#if FK_YAML_HAS_CHAR8_T
/// @brief The partial specialization of utf_encode_detector for char8_t iterators.
/// @tparam ItrType An iterator type.
template <typename ItrType>
struct utf_encode_detector<ItrType, enable_if_t<is_iterator_of<ItrType, char8_t>::value>> {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param begin The iterator to the first element of an input.
/// @param end The iterator to the past-the end element of an input.
/// @return A detected encoding type.
static utf_encode_t detect(ItrType& begin, const ItrType& end) {
if FK_YAML_UNLIKELY (begin == end) {
return utf_encode_t::UTF_8;
}
std::array<uint8_t, 4> bytes {};
bytes.fill(0xFFu);
auto current = begin;
for (int i = 0; i < 4 && current != end; i++, ++current) {
bytes[i] = uint8_t(*current); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
if FK_YAML_UNLIKELY (encode_type != utf_encode_t::UTF_8) {
throw exception("char8_t characters must be encoded in the UTF-8 format.");
}
if (has_bom) {
// skip reading the BOM.
std::advance(begin, 3);
}
return encode_type;
}
};
#endif // FK_YAML_HAS_CHAR8_T
/// @brief The partial specialization of utf_encode_detector for char16_t iterators.
/// @tparam ItrType An iterator type.
template <typename ItrType>
struct utf_encode_detector<ItrType, enable_if_t<is_iterator_of<ItrType, char16_t>::value>> {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param begin The iterator to the first element of an input.
/// @param end The iterator to the past-the end element of an input.
/// @return A detected encoding type.
static utf_encode_t detect(ItrType& begin, const ItrType& end) {
if FK_YAML_UNLIKELY (begin == end) {
return utf_encode_t::UTF_16BE;
}
// the inner curly braces are necessary for older compilers
std::array<uint8_t, 4> bytes {{}};
bytes.fill(0xFFu);
auto current = begin;
for (int i = 0; i < 2 && current != end; i++, ++current) {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-constant-array-index)
const char16_t elem = *current;
const int idx_base = i * 2;
bytes[idx_base] = static_cast<uint8_t>(elem >> 8);
bytes[idx_base + 1] = static_cast<uint8_t>(elem);
// NOLINTEND(cppcoreguidelines-pro-bounds-constant-array-index)
}
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
if FK_YAML_UNLIKELY (encode_type != utf_encode_t::UTF_16BE && encode_type != utf_encode_t::UTF_16LE) {
throw exception("char16_t characters must be encoded in the UTF-16 format.");
}
if (has_bom) {
// skip reading the BOM.
std::advance(begin, 1);
}
return encode_type;
}
};
/// @brief The partial specialization of utf_encode_detector for char32_t iterators.
/// @tparam ItrType An iterator type.
template <typename ItrType>
struct utf_encode_detector<ItrType, enable_if_t<is_iterator_of<ItrType, char32_t>::value>> {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param begin The iterator to the first element of an input.
/// @param end The iterator to the past-the end element of an input.
/// @return A detected encoding type.
static utf_encode_t detect(ItrType& begin, const ItrType& end) {
if FK_YAML_UNLIKELY (begin == end) {
return utf_encode_t::UTF_32BE;
}
// the inner curly braces are necessary for older compilers
std::array<uint8_t, 4> bytes {{}};
const char32_t elem = *begin;
bytes[0] = static_cast<uint8_t>(elem >> 24);
bytes[1] = static_cast<uint8_t>(elem >> 16);
bytes[2] = static_cast<uint8_t>(elem >> 8);
bytes[3] = static_cast<uint8_t>(elem);
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
if FK_YAML_UNLIKELY (encode_type != utf_encode_t::UTF_32BE && encode_type != utf_encode_t::UTF_32LE) {
throw exception("char32_t characters must be encoded in the UTF-32 format.");
}
if (has_bom) {
// skip reading the BOM.
std::advance(begin, 1);
}
return encode_type;
}
};
/// @brief A class which detects UTF encoding type and the existence of a BOM from the input file.
struct file_utf_encode_detector {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param p_file The input file handle.
/// @return A detected encoding type.
static utf_encode_t detect(std::FILE* p_file) noexcept {
// the inner curly braces are necessary for older compilers
std::array<uint8_t, 4> bytes {{}};
bytes.fill(0xFFu);
for (int i = 0; i < 4; i++) {
char byte = 0;
const std::size_t size = std::fread(&byte, sizeof(char), 1, p_file);
if (size != sizeof(char)) {
break;
}
bytes[i] = static_cast<uint8_t>(byte & 0xFF); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
// move back to the beginning if a BOM doesn't exist.
long offset = 0; // NOLINT(google-runtime-int)
if (has_bom) {
switch (encode_type) {
case utf_encode_t::UTF_8:
offset = 3;
break;
case utf_encode_t::UTF_16BE:
case utf_encode_t::UTF_16LE:
offset = 2;
break;
case utf_encode_t::UTF_32BE:
case utf_encode_t::UTF_32LE:
offset = 4;
break;
}
}
std::fseek(p_file, offset, SEEK_SET); // NOLINT(cert-err33-c)
return encode_type;
}
};
/// @brief A class which detects UTF encoding type and the existence of a BOM from the input file.
struct stream_utf_encode_detector {
/// @brief Detects the encoding type of the input, and consumes a BOM if it exists.
/// @param p_file The input file handle.
/// @return A detected encoding type.
static utf_encode_t detect(std::istream& is) noexcept {
// the inner curly braces are necessary for older compilers
std::array<uint8_t, 4> bytes {{}};
bytes.fill(0xFFu);
for (int i = 0; i < 4; i++) {
char ch = 0;
is.read(&ch, 1);
const std::streamsize size = is.gcount();
if (size != 1) {
// without this, seekg() will fail.
is.clear();
break;
}
bytes[i] = static_cast<uint8_t>(ch & 0xFF); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
bool has_bom = false;
const utf_encode_t encode_type = detect_encoding_type(bytes, has_bom);
// move back to the beginning if a BOM doesn't exist.
std::streamoff offset = 0;
if (has_bom) {
switch (encode_type) {
case utf_encode_t::UTF_8:
offset = 3;
break;
case utf_encode_t::UTF_16BE:
case utf_encode_t::UTF_16LE:
offset = 2;
break;
case utf_encode_t::UTF_32BE:
case utf_encode_t::UTF_32LE:
offset = 4;
break;
}
}
is.seekg(offset, std::ios_base::beg);
return encode_type;
}
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_DETECTOR_HPP */
// #include <fkYAML/detail/encodings/utf_encode_t.hpp>
// #include <fkYAML/detail/encodings/utf_encodings.hpp>
// #include <fkYAML/detail/meta/input_adapter_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/str_view.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
///////////////////////
// input_adapter //
///////////////////////
template <typename IterType, typename = void>
class iterator_input_adapter;
/// @brief An input adapter for iterators of type char.
/// @tparam IterType An iterator type.
template <typename IterType>
class iterator_input_adapter<IterType, enable_if_t<is_iterator_of<IterType, char>::value>> {
public:
/// @brief Construct a new iterator_input_adapter object.
iterator_input_adapter() = default;
/// @brief Construct a new iterator_input_adapter object.
/// @param begin The beginning of iterators.
/// @param end The end of iterators.
/// @param encode_type The encoding type for this input adapter.
/// @param is_contiguous Whether iterators are contiguous or not.
iterator_input_adapter(IterType begin, IterType end, utf_encode_t encode_type, bool is_contiguous) noexcept
: m_begin(begin),
m_end(end),
m_encode_type(encode_type),
m_is_contiguous(is_contiguous) {
}
// allow only move construct/assignment like other input adapters.
iterator_input_adapter(const iterator_input_adapter&) = delete;
iterator_input_adapter(iterator_input_adapter&& rhs) = default;
iterator_input_adapter& operator=(const iterator_input_adapter&) = delete;
iterator_input_adapter& operator=(iterator_input_adapter&&) = default;
~iterator_input_adapter() = default;
/// @brief Get view into the input buffer contents.
/// @return View into the input buffer contents.
str_view get_buffer_view() {
if FK_YAML_UNLIKELY (m_begin == m_end) {
return {};
}
m_buffer.clear();
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);
IterType current = m_begin;
std::deque<IterType> cr_itrs {};
while (current != m_end) {
const auto first = static_cast<uint8_t>(*current);
const uint32_t num_bytes = utf8::get_num_bytes(first);
switch (num_bytes) {
case 1:
if FK_YAML_UNLIKELY (first == 0x0D /*CR*/) {
cr_itrs.emplace_back(current);
}
break;
case 2: {
const auto second = static_cast<uint8_t>(*++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<uint8_t>(*++current);
const auto third = static_cast<uint8_t>(*++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<uint8_t>(*++current);
const auto third = static_cast<uint8_t>(*++current);
const auto fourth = static_cast<uint8_t>(*++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
}
++current;
}
const bool is_contiguous_no_cr = cr_itrs.empty() && m_is_contiguous;
if FK_YAML_LIKELY (is_contiguous_no_cr) {
// The input iterators (begin, end) can be used as-is during parsing.
FK_YAML_ASSERT(m_begin != m_end);
return str_view {&*m_begin, static_cast<std::size_t>(std::distance(m_begin, m_end))};
}
m_buffer.reserve(std::distance(m_begin, m_end) - cr_itrs.size());
current = m_begin;
for (const auto& cr_itr : cr_itrs) {
m_buffer.append(current, cr_itr);
current = std::next(cr_itr);
}
m_buffer.append(current, m_end);
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);
// Assume the input characters are all ASCII characters.
// That's the most probably the case.
m_buffer.reserve(std::distance(m_begin, m_end) / 2);
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;
}
std::array<char16_t, 2> encoded_buffer {{0, 0}};
uint32_t encoded_buf_size {0};
std::array<uint8_t, 4> utf8_buffer {{0, 0, 0, 0}};
uint32_t utf8_buf_size {0};
IterType current = m_begin;
while (current != m_end || encoded_buf_size != 0) {
while (current != m_end && encoded_buf_size < 2) {
auto utf16 = static_cast<char16_t>(static_cast<uint8_t>(*current) << shift_bits[0]);
utf16 |= static_cast<char16_t>(static_cast<uint8_t>(*++current) << shift_bits[1]);
++current;
// skip appending CRs.
if FK_YAML_LIKELY (utf16 != char16_t(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<const char*>(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);
// Assume the input characters are all ASCII characters.
// That's the most probably the case.
m_buffer.reserve(std::distance(m_begin, m_end) / 4);
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;
}
std::array<uint8_t, 4> utf8_buffer {{0, 0, 0, 0}};
uint32_t utf8_buf_size {0};
IterType current = m_begin;
while (current != m_end) {
auto utf32 = static_cast<char32_t>(*current << shift_bits[0]);
++current;
utf32 |= static_cast<char32_t>(*current << shift_bits[1]);
++current;
utf32 |= static_cast<char32_t>(*current << shift_bits[2]);
++current;
utf32 |= static_cast<char32_t>(*current << shift_bits[3]);
++current;
if FK_YAML_LIKELY (utf32 != char32_t(0x0000000Du)) {
utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size);
m_buffer.append(reinterpret_cast<const char*>(utf8_buffer.data()), utf8_buf_size);
}
}
return str_view {m_buffer.begin(), m_buffer.end()};
}
private:
/// The iterator at the beginning of input.
IterType m_begin {};
/// The iterator at the end of input.
IterType m_end {};
/// 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;
/// Whether ItrType is a contiguous iterator.
bool m_is_contiguous {false};
};
#if FK_YAML_HAS_CHAR8_T
/// @brief An input adapter for iterators of type char8_t.
/// @tparam IterType An iterator type.
template <typename IterType>
class iterator_input_adapter<IterType, enable_if_t<is_iterator_of<IterType, char8_t>::value>> {
public:
/// @brief Construct a new iterator_input_adapter object.
iterator_input_adapter() = default;
/// @brief Construct a new iterator_input_adapter object.
/// @param begin The beginning of iterators.
/// @param end The end of iterators.
/// @param encode_type The encoding type for this input adapter.
/// @param is_contiguous Whether iterators are contiguous or not.
iterator_input_adapter(IterType begin, IterType end, utf_encode_t encode_type, bool is_contiguous) noexcept
: m_begin(begin),
m_end(end),
m_encode_type(encode_type),
m_is_contiguous(is_contiguous) {
// char8_t characters must be encoded in the UTF-8 format.
// See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0482r6.html.
FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_8);
}
// allow only move construct/assignment like other input adapters.
iterator_input_adapter(const iterator_input_adapter&) = delete;
iterator_input_adapter(iterator_input_adapter&& rhs) = default;
iterator_input_adapter& operator=(const iterator_input_adapter&) = delete;
iterator_input_adapter& operator=(iterator_input_adapter&&) = default;
~iterator_input_adapter() = default;
/// @brief Get view into the input buffer contents.
/// @return View into the input buffer contents.
str_view get_buffer_view() {
if FK_YAML_UNLIKELY (m_begin == m_end) {
return {};
}
IterType current = m_begin;
std::deque<IterType> cr_itrs {};
while (current != m_end) {
const auto first = static_cast<uint8_t>(*current);
const uint32_t num_bytes = utf8::get_num_bytes(first);
switch (num_bytes) {
case 1:
if FK_YAML_UNLIKELY (first == 0x0D /*CR*/) {
cr_itrs.emplace_back(current);
}
break;
case 2: {
const auto second = static_cast<uint8_t>(*++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<uint8_t>(*++current);
const auto third = static_cast<uint8_t>(*++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<uint8_t>(*++current);
const auto third = static_cast<uint8_t>(*++current);
const auto fourth = static_cast<uint8_t>(*++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
}
++current;
}
m_buffer.reserve(std::distance(m_begin, m_end) - cr_itrs.size());
current = m_begin;
for (const auto& cr_itr : cr_itrs) {
std::transform(
current, cr_itr, std::back_inserter(m_buffer), [](char8_t c) { return static_cast<char>(c); });
current = std::next(cr_itr);
}
std::transform(current, m_end, std::back_inserter(m_buffer), [](char8_t c) { return static_cast<char>(c); });
return str_view {m_buffer.begin(), m_buffer.end()};
}
private:
/// The iterator at the beginning of input.
IterType m_begin {};
/// The iterator at the end of input.
IterType m_end {};
/// 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;
/// Whether ItrType is a contiguous iterator.
bool m_is_contiguous {false};
};
#endif // FK_YAML_HAS_CHAR8_T
/// @brief An input adapter for iterators of type char16_t.
/// @tparam IterType An iterator type.
template <typename IterType>
class iterator_input_adapter<IterType, enable_if_t<is_iterator_of<IterType, char16_t>::value>> {
public:
/// @brief Construct a new iterator_input_adapter object.
iterator_input_adapter() = default;
/// @brief Construct a new iterator_input_adapter object.
/// @param begin The beginning of iterators.
/// @param end The end of iterators.
/// @param encode_type The encoding type for this input adapter.
/// @param is_contiguous Whether iterators are contiguous or not.
iterator_input_adapter(IterType begin, IterType end, utf_encode_t encode_type, bool is_contiguous) noexcept
: m_begin(begin),
m_end(end),
m_encode_type(encode_type),
m_is_contiguous(is_contiguous) {
FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_16BE || m_encode_type == utf_encode_t::UTF_16LE);
}
// allow only move construct/assignment like other input adapters.
iterator_input_adapter(const iterator_input_adapter&) = delete;
iterator_input_adapter(iterator_input_adapter&& rhs) = default;
iterator_input_adapter& operator=(const iterator_input_adapter&) = delete;
iterator_input_adapter& operator=(iterator_input_adapter&&) = default;
~iterator_input_adapter() = default;
/// @brief Get view into the input buffer contents.
/// @return View into the input buffer contents.
str_view get_buffer_view() {
if FK_YAML_UNLIKELY (m_begin == m_end) {
return {};
}
const int shift_bits = (m_encode_type == utf_encode_t::UTF_16BE) ? 0 : 8;
std::array<char16_t, 2> encoded_buffer {{0, 0}};
uint32_t encoded_buf_size {0};
std::array<uint8_t, 4> utf8_buffer {{0, 0, 0, 0}};
uint32_t utf8_buf_size {0};
// Assume the input characters are all ASCII characters.
// That's the most probably the case.
m_buffer.reserve(std::distance(m_begin, m_end));
IterType current = m_begin;
while (current != m_end || encoded_buf_size != 0) {
while (current != m_end && encoded_buf_size < 2) {
char16_t utf16 = *current;
++current;
utf16 = static_cast<char16_t>(((utf16 & 0x00FFu) << shift_bits) | ((utf16 & 0xFF00u) >> shift_bits));
if FK_YAML_LIKELY (utf16 != char16_t(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_buffer[1] = 0;
}
encoded_buf_size -= consumed_size;
m_buffer.append(reinterpret_cast<const char*>(utf8_buffer.data()), utf8_buf_size);
}
return str_view {m_buffer.begin(), m_buffer.end()};
}
private:
/// The iterator at the beginning of input.
IterType m_begin {};
/// The iterator at the end of input.
IterType m_end {};
/// The encoding type for this input adapter.
utf_encode_t m_encode_type {utf_encode_t::UTF_16BE};
/// The normalized owned buffer.
std::string m_buffer;
/// Whether ItrType is a contiguous iterator.
bool m_is_contiguous {false};
};
/// @brief An input adapter for iterators of type char32_t.
/// @tparam IterType An iterator type.
template <typename IterType>
class iterator_input_adapter<IterType, enable_if_t<is_iterator_of<IterType, char32_t>::value>> {
public:
/// @brief Construct a new iterator_input_adapter object.
iterator_input_adapter() = default;
/// @brief Construct a new iterator_input_adapter object.
/// @param begin The beginning of iterators.
/// @param end The end of iterators.
/// @param encode_type The encoding type for this input adapter.
/// @param is_contiguous Whether iterators are contiguous or not.
iterator_input_adapter(IterType begin, IterType end, utf_encode_t encode_type, bool is_contiguous) noexcept
: m_begin(begin),
m_end(end),
m_encode_type(encode_type),
m_is_contiguous(is_contiguous) {
FK_YAML_ASSERT(m_encode_type == utf_encode_t::UTF_32BE || m_encode_type == utf_encode_t::UTF_32LE);
}
// allow only move construct/assignment like other input adapters.
iterator_input_adapter(const iterator_input_adapter&) = delete;
iterator_input_adapter(iterator_input_adapter&& rhs) = default;
iterator_input_adapter& operator=(const iterator_input_adapter&) = delete;
iterator_input_adapter& operator=(iterator_input_adapter&&) = default;
~iterator_input_adapter() = default;
/// @brief Get view into the input buffer contents.
/// @return View into the input buffer contents.
str_view get_buffer_view() {
if FK_YAML_UNLIKELY (m_begin == m_end) {
return {};
}
int shift_bits[4] {0, 0, 0, 0};
if (m_encode_type == utf_encode_t::UTF_32LE) {
shift_bits[0] = 24;
shift_bits[1] = 8;
shift_bits[2] = 8;
shift_bits[3] = 24;
}
std::array<uint8_t, 4> utf8_buffer {{0, 0, 0, 0}};
uint32_t utf8_buf_size {0};
// Assume the input characters are all ASCII characters.
// That's the most probably the case.
m_buffer.reserve(std::distance(m_begin, m_end));
IterType current = m_begin;
while (current != m_end) {
const char32_t tmp = *current;
++current;
const auto utf32 = static_cast<char32_t>(
((tmp & 0xFF000000u) >> shift_bits[0]) | ((tmp & 0x00FF0000u) >> shift_bits[1]) |
((tmp & 0x0000FF00u) << shift_bits[2]) | ((tmp & 0x000000FFu) << shift_bits[3]));
if FK_YAML_UNLIKELY (utf32 != static_cast<char32_t>(0x0000000Du)) {
utf8::from_utf32(utf32, utf8_buffer, utf8_buf_size);
m_buffer.append(reinterpret_cast<const char*>(utf8_buffer.data()), utf8_buf_size);
}
}
return str_view {m_buffer.begin(), m_buffer.end()};
}
private:
/// The iterator at the beginning of input.
IterType m_begin {};
/// The iterator at the end of input.
IterType m_end {};
/// The encoding type for this input adapter.
utf_encode_t m_encode_type {utf_encode_t::UTF_32BE};
/// The normalized owned buffer.
std::string m_buffer;
/// Whether ItrType is a contiguous iterator.
bool m_is_contiguous {false};
};
/// @brief An input adapter for C-style file handles.
class file_input_adapter {
public:
/// @brief Construct a new file_input_adapter object.
file_input_adapter() = default;
/// @brief Construct a new file_input_adapter object.
/// @note
/// This class doesn't call fopen() nor fclose().
/// It's user's responsibility to call those functions.
/// @param file A file handle for this adapter. (A non-null pointer is assumed.)
/// @param encode_type The encoding type for this input adapter.
explicit file_input_adapter(std::FILE* file, utf_encode_t encode_type) noexcept
: m_file(file),
m_encode_type(encode_type) {
}
// allow only move construct/assignment
file_input_adapter(const file_input_adapter&) = delete;
file_input_adapter(file_input_adapter&& rhs) = default;
file_input_adapter& operator=(const file_input_adapter&) = delete;
file_input_adapter& operator=(file_input_adapter&&) = default;
~file_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] {};
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<uint8_t>(*current++);
const uint32_t num_bytes = utf8::get_num_bytes(first);
switch (num_bytes) {
case 1:
break;
case 2: {
const auto second = static_cast<uint8_t>(*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<uint8_t>(*current++);
const auto third = static_cast<uint8_t>(*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<uint8_t>(*current++);
const auto third = static_cast<uint8_t>(*current++);
const auto fourth = static_cast<uint8_t>(*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<char16_t, 2> encoded_buffer {{0, 0}};
uint32_t encoded_buf_size {0};
std::array<uint8_t, 4> 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<char16_t>(
(static_cast<uint8_t>(chars[0]) << shift_bits[0]) |
(static_cast<uint8_t>(chars[1]) << shift_bits[1]));
if FK_YAML_LIKELY (utf16 != static_cast<char16_t>(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<const char*>(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<uint8_t, 4> 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<char32_t>(
(static_cast<uint8_t>(chars[0]) << shift_bits[0]) | (static_cast<uint8_t>(chars[1]) << shift_bits[1]) |
(static_cast<uint8_t>(chars[2]) << shift_bits[2]) | (static_cast<uint8_t>(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<const char*>(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<std::size_t>(m_istream->gcount());
if FK_YAML_UNLIKELY (read_size == 0) {
break;
}
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);
} while (!m_istream->eof());
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<uint8_t>(*current++);
const uint32_t num_bytes = utf8::get_num_bytes(first);
switch (num_bytes) {
case 1:
break;
case 2: {
const auto second = static_cast<uint8_t>(*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<uint8_t>(*current++);
const auto third = static_cast<uint8_t>(*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<uint8_t>(*current++);
const auto third = static_cast<uint8_t>(*current++);
const auto fourth = static_cast<uint8_t>(*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<char16_t, 2> encoded_buffer {{0, 0}};
uint32_t encoded_buf_size {0};
std::array<uint8_t, 4> 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;
}
const auto utf16 = static_cast<char16_t>(
(static_cast<uint8_t>(chars[0]) << shift_bits[0]) |
(static_cast<uint8_t>(chars[1]) << shift_bits[1]));
if FK_YAML_LIKELY (utf16 != static_cast<char16_t>(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<const char*>(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<uint8_t, 4> 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<char32_t>(
(static_cast<uint8_t>(chars[0]) << shift_bits[0]) | (static_cast<uint8_t>(chars[1]) << shift_bits[1]) |
(static_cast<uint8_t>(chars[2]) << shift_bits[2]) | (static_cast<uint8_t>(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<const char*>(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 <typename ItrType>
inline iterator_input_adapter<ItrType> create_iterator_input_adapter(ItrType begin, ItrType end, bool is_contiguous) {
const utf_encode_t encode_type = utf_encode_detector<ItrType>::detect(begin, end);
return iterator_input_adapter<ItrType>(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<ItrType> An iterator_input_adapter object for the target iterator type.
template <typename ItrType>
inline iterator_input_adapter<ItrType> 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<typename std::iterator_traits<ItrType>::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 <typename T, std::size_t N>
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 <typename ContainerType, typename = void>
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 <typename ContainerType>
struct container_input_adapter_factory<
ContainerType, void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>> {
/// A type for resulting input adapter object.
using adapter_type =
decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
/// @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<ContainerType>::adapter_type
template <typename ContainerType>
inline typename input_adapter_factory::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(
const ContainerType& container) {
return input_adapter_factory::container_input_adapter_factory<ContainerType>::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/detail/iterator.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_ITERATOR_HPP
#define FK_YAML_DETAIL_ITERATOR_HPP
#include <cstddef>
#include <iterator>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/exception.hpp>
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 <typename ValueType>
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 <typename ValueType>
struct iterator_traits<const ValueType> {
/// 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 <typename BasicNodeType>
struct iterator_holder {
static_assert(
is_basic_node<BasicNodeType>::value,
"iterator_holder class only accepts a basic_node as its template parameter.");
/// A sequence iterator object.
typename BasicNodeType::sequence_type::iterator sequence_iterator {};
/// A mapping iterator object.
typename BasicNodeType::mapping_type::iterator mapping_iterator {};
};
/// @brief A class which holds iterators either of sequence or mapping type
/// @tparam ValueType The type of iterated elements.
template <typename ValueType>
class iterator {
/// @brief The iterator type with ValueType of different const-ness.
using other_iterator_type = typename std::conditional<
std::is_const<ValueType>::value, iterator<typename std::remove_const<ValueType>::type>,
iterator<const ValueType>>::type;
friend other_iterator_type;
public:
/// A type for iterator traits of instantiated @Iterator template class.
using iterator_traits_type = iterator_traits<ValueType>;
/// 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;
static_assert(is_basic_node<value_type>::value, "iterator class only accepts a basic_node as its value type.");
/// @brief Constructs an iterator object.
iterator() = default;
/// @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 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 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_same<OtherIterator, other_iterator_type>, std::is_const<ValueType>>::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 A copy assignment operator of the iterator class.
iterator& operator=(const iterator&) = default;
template <
typename OtherIterator,
enable_if_t<
conjunction<std::is_same<OtherIterator, other_iterator_type>, std::is_const<ValueType>>::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 Move constructs an iterator.
iterator(iterator&&) = default;
/// @brief A move assignment operator of the iterator class.
iterator& operator=(iterator&&) = default;
/// @brief Destroys an iterator.
~iterator() = default;
/// @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));
}
// m_inner_iterator_type == iterator_t::MAPPING:
return &(m_iterator_holder.mapping_iterator->second);
}
/// @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);
}
// m_inner_iterator_type == iterator_t::MAPPING:
return m_iterator_holder.mapping_iterator->second;
}
/// @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;
}
/// @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;
}
/// @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;
}
/// @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;
}
/// @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);
}
/// @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;
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::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);
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::value, int> = 0>
bool operator!=(const Iterator& rhs) const {
return !operator==(rhs);
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::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 FK_YAML_UNLIKELY (m_inner_iterator_type == iterator_t::MAPPING) {
throw fkyaml::exception("Cannot compare order of iterators of the mapping container type");
}
return (m_iterator_holder.sequence_iterator < rhs.m_iterator_holder.sequence_iterator);
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::value, int> = 0>
bool operator<=(const Iterator& rhs) const {
return !rhs.operator<(*this);
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::value, int> = 0>
bool operator>(const Iterator& rhs) const {
return !operator<=(rhs);
}
/// @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<Iterator, iterator>, std::is_same<Iterator, other_iterator_type>>::value, int> = 0>
bool operator>=(const Iterator& rhs) const {
return !operator<(rhs);
}
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 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.");
}
return m_iterator_holder.mapping_iterator->first;
}
/// @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 type of the internally-held iterator.
iterator_t m_inner_iterator_type {iterator_t::SEQUENCE};
/// A holder of actual iterators.
iterator_holder<value_type> 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 <std::size_t I, typename ValueType, enable_if_t<I == 0, int> = 0>
inline auto get(const iterator<ValueType>& 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 <std::size_t I, typename ValueType, enable_if_t<I == 1, int> = 0>
inline auto get(const iterator<ValueType>& i) -> decltype(i.value()) {
return i.value();
}
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 Partial specialization of std::tuple_size for iterator class.
/// @tparam ValueType The iterator value type.
template <typename ValueType>
// NOLINTNEXTLINE(cert-dcl58-cpp)
struct tuple_size<::fkyaml::detail::iterator<ValueType>> : integral_constant<size_t, 2> {};
/// @brief Partial specialization of std::tuple_element for iterator class.
/// @tparam ValueType The iterator value type.
/// @tparam I The element index.
template <size_t I, typename ValueType>
// NOLINTNEXTLINE(cert-dcl58-cpp)
struct tuple_element<I, ::fkyaml::detail::iterator<ValueType>> {
using type = decltype(get<I>(std::declval<::fkyaml::detail::iterator<ValueType>>()));
};
#ifdef __clang__
#pragma clang diagnostic pop
#endif
} // namespace std
#endif /* FK_YAML_DETAIL_ITERATOR_HPP */
// #include <fkYAML/detail/map_range_proxy.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP
#define FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A helper iterator class which wraps a mapping iterator object.
/// @tparam Iterator The base iterator type.
template <typename Iterator>
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;
}
/// @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 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();
}
private:
/// The base iterator object.
Iterator m_base_iterator {};
};
/// @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 <typename BasicNodeType>
class map_range_proxy {
static_assert(
is_basic_node<BasicNodeType>::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<typename std::conditional<
std::is_const<BasicNodeType>::value, typename BasicNodeType::const_iterator,
typename BasicNodeType::iterator>::type>;
/// @brief The type of const iterators.
using const_iterator = map_iterator_proxy<typename BasicNodeType::const_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 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 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 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 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()};
}
private:
/// Pointer to the mapping node object. (non-null)
BasicNodeType* mp_map {nullptr};
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP */
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/detail/node_attrs.hpp>
// #include <fkYAML/detail/node_property.hpp>
// #include <fkYAML/detail/node_ref_storage.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_NODE_REF_STORAGE_HPP
#define FK_YAML_DETAIL_NODE_REF_STORAGE_HPP
#include <initializer_list>
#include <type_traits>
#include <utility>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @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 <typename BasicNodeType>
class node_ref_storage {
static_assert(is_basic_node<BasicNodeType>::value, "node_ref_storage only accepts basic_node<...>");
using node_type = BasicNodeType;
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<node_type>::value)
: m_owned_value(std::move(n)) {
}
/// @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 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<node_ref_storage> init)
: m_owned_value(init) {
}
/// @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 <typename... Args, enable_if_t<std::is_constructible<node_type, Args...>::value, int> = 0>
node_ref_storage(Args&&... args)
: m_owned_value(std::forward<Args>(args)...) {
}
// 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;
~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 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_NODE_REF_STORAGE_HPP */
// #include <fkYAML/detail/output/serializer.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP
#define FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP
#include <cmath>
#include <sstream>
#include <string>
#include <vector>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/conversions/to_string.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP
#define FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP
#include <cmath>
#include <limits>
#include <string>
#include <sstream>
#include <type_traits>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
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 <typename ValueType, typename CharType>
inline void to_string(ValueType v, std::basic_string<CharType>& 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 <typename IntegerType>
inline enable_if_t<is_non_bool_integral<IntegerType>::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 <typename FloatType>
inline enable_if_t<std::is_floating_point<FloatType>::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<FloatType>::infinity()) {
s = ".inf";
}
else {
s = "-.inf";
}
return;
}
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_END
#endif /* FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP */
// #include <fkYAML/detail/encodings/yaml_escaper.hpp>
// #include <fkYAML/detail/input/scalar_scanner.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/exception.hpp>
// #include <fkYAML/node_type.hpp>
// #include <fkYAML/yaml_version_type.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief A basic implementation of serialization feature for YAML nodes.
/// @tparam BasicNodeType A BasicNode template class instantiation.
template <typename BasicNodeType>
class basic_serializer {
static_assert(detail::is_basic_node<BasicNodeType>::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<BasicNodeType>& docs) {
std::string str {};
const auto size = static_cast<uint32_t>(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 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;
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;
}
if (!p_meta->primary_handle_prefix.empty()) {
str += "%TAG ! ";
str += p_meta->primary_handle_prefix;
str += "\n";
needs_directive_end = true;
}
if (!p_meta->secondary_handle_prefix.empty()) {
str += "%TAG !! ";
str += p_meta->secondary_handle_prefix;
str += "\n";
needs_directive_end = true;
}
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;
}
if (needs_directive_end) {
str += "---\n";
}
return needs_directive_end;
}
/// @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 += "-";
const bool is_appended = try_append_alias(seq_item, true, str);
if (is_appended) {
str += "\n";
continue;
}
try_append_anchor(seq_item, true, str);
try_append_tag(seq_item, true, str);
const bool is_scalar = seq_item.is_scalar();
if (is_scalar) {
str += " ";
serialize_node(seq_item, cur_indent, str);
str += "\n";
continue;
}
const bool is_empty = seq_item.empty();
if (!is_empty) {
str += "\n";
serialize_node(seq_item, cur_indent + 2, str);
continue;
}
// 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);
// serialize a mapping key node.
const auto& key_node = itr.key();
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 += " ";
}
const bool is_container = !key_node.is_scalar();
if (is_container) {
str += "? ";
}
const auto indent = static_cast<uint32_t>(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);
}
}
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;
}
try_append_anchor(value_node, true, str);
try_append_tag(value_node, true, str);
const bool is_scalar = itr->is_scalar();
if (is_scalar) {
str += " ";
serialize_node(value_node, cur_indent, str);
str += "\n";
continue;
}
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<typename BasicNodeType::boolean_type>(), m_tmp_str_buff);
str += m_tmp_str_buff;
break;
case node_type::INTEGER:
to_string(node.template get_value<typename BasicNodeType::integer_type>(), m_tmp_str_buff);
str += m_tmp_str_buff;
break;
case node_type::FLOAT:
to_string(node.template get_value<typename BasicNodeType::float_number_type>(), 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);
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;
}
// 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
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;
}
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;
}
str.append(indent - get_cur_indent(str), ' ');
}
/// @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 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 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 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());
const auto& s = node.as_str();
return yaml_escaper::escape(s.c_str(), s.c_str() + s.size(), is_escaped);
} // LCOV_EXCL_LINE
private:
/// A temporal buffer for conversion from a scalar to a string.
std::string m_tmp_str_buff;
};
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP */
// #include <fkYAML/detail/reverse_iterator.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_REVERSE_ITERATOR_HPP
#define FK_YAML_DETAIL_REVERSE_ITERATOR_HPP
#include <iterator>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @brief An iterator adapter class that reverses the direction of a given node iterator.
/// @tparam Iterator The base iterator type.
template <typename Iterator>
class reverse_iterator {
static_assert(
is_basic_node<typename Iterator::value_type>::value,
"reverse_iterator only accepts a basic_node type as the underlying iterator's value type");
public:
/// @brief The base iterator type.
using iterator_type = Iterator;
/// @brief The base iterator category.
using iterator_category = typename Iterator::iterator_category;
/// @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;
/// @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;
/// @brief Constructs a reverse_iterator object.
reverse_iterator() = default;
/// @brief Copy constructs a reverse_iterator object.
reverse_iterator(const reverse_iterator&) = default;
/// @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;
/// @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 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 <typename U, enable_if_t<negation<std::is_same<U, Iterator>>::value, int> = 0>
reverse_iterator(const reverse_iterator<U>& other) noexcept
: m_current(other.base()) {
}
/// @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 <typename U, enable_if_t<negation<std::is_same<U, Iterator>>::value, int> = 0>
reverse_iterator& operator=(const reverse_iterator<U>& other) noexcept {
m_current = other.base();
return *this;
}
/// @brief Destructs a reverse_iterator object.
~reverse_iterator() = default;
/// @brief Accesses the underlying iterator object.
/// @return The underlying iterator object.
Iterator base() const noexcept {
return m_current;
}
/// @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;
}
/// @brief Get pointer to the pointed-to element.
/// @return Pointer to the pointed-to element.
pointer operator->() const noexcept {
return &(operator*());
}
/// @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 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 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;
}
/// @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 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);
}
/// @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 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);
}
/// @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 Get the mapping key node of the underlying iterator.
/// @return The mapping key node of the underlying iterator.
auto key() const -> decltype(std::declval<Iterator>().key()) {
Iterator itr = --(base());
return itr.key();
}
/// @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 <typename IteratorL, typename IteratorR>
inline bool operator==(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& 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 <typename IteratorL, typename IteratorR>
inline bool operator!=(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& 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 <typename IteratorL, typename IteratorR>
inline bool operator<(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& rhs) {
return lhs.base() > rhs.base();
}
/// @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 <typename IteratorL, typename IteratorR>
inline bool operator<=(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& rhs) {
return lhs.base() >= rhs.base();
}
/// @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 <typename IteratorL, typename IteratorR>
inline bool operator>(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& rhs) {
return lhs.base() < rhs.base();
}
/// @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 <typename IteratorL, typename IteratorR>
inline bool operator>=(const reverse_iterator<IteratorL>& lhs, const reverse_iterator<IteratorR>& rhs) {
return lhs.base() <= rhs.base();
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_REVERSE_ITERATOR_HPP */
// #include <fkYAML/detail/types/node_t.hpp>
// #include <fkYAML/detail/types/yaml_version_t.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP
#define FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP
#include <cstdint>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/yaml_version_type.hpp>
FK_YAML_DETAIL_NAMESPACE_BEGIN
/// @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;
}
}
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;
}
}
FK_YAML_DETAIL_NAMESPACE_END
#endif /* FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP */
// #include <fkYAML/exception.hpp>
// #include <fkYAML/node_type.hpp>
// #include <fkYAML/node_value_converter.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_NODE_VALUE_CONVERTER_HPP
#define FK_YAML_NODE_VALUE_CONVERTER_HPP
#include <utility>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/conversions/from_node.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP
#define FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP
#include <array>
#include <cmath>
#include <forward_list>
#include <limits>
#include <utility>
#include <valarray>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/detail/types/node_t.hpp>
// #include <fkYAML/exception.hpp>
#ifdef FK_YAML_HAS_CXX_17
#include <optional>
#endif
FK_YAML_DETAIL_NAMESPACE_BEGIN
///////////////////
// from_node //
///////////////////
// utility type traits and functors
/// @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 <typename T>
using is_constructible_mapping_type =
conjunction<detect::has_key_type<T>, detect::has_mapped_type<T>, detect::has_value_type<T>>;
/// @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 <typename BasicNodeType, typename T>
using is_constructible_sequence_type = conjunction<
negation<is_basic_node<T>>, detect::has_iterator<T>, detect::is_iterator_traits<typename T::iterator>,
detect::has_begin_end<T>, negation<std::is_same<T, typename BasicNodeType::mapping_type>>,
negation<is_constructible_mapping_type<T>>>;
/// @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 <typename T>
using is_sequence_container_adapter = conjunction<
negation<is_basic_node<T>>, detect::has_container_type<T>, detect::has_value_type<T>,
negation<detect::has_key_type<T>>>;
/// @brief Helper struct for reserve() member function call switch for types which do not have reserve function.
/// @tparam ContainerType A container type.
template <typename ContainerType, typename = void>
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 Helper struct for reserve() member function call switch for types which have reserve function.
/// @tparam ContainerType A container type.
template <typename ContainerType>
struct call_reserve_if_available<ContainerType, enable_if_t<detect::has_reserve<ContainerType>::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);
}
};
// 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 array An array object.
template <typename BasicNodeType, typename T, std::size_t N>
inline auto from_node(const BasicNodeType& n, T (&array)[N])
-> decltype(n.get_value_inplace(std::declval<T&>()), 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]);
}
}
/// @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 <typename BasicNodeType, typename T, std::size_t N0, std::size_t N1>
inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1])
-> decltype(n.get_value_inplace(std::declval<T&>()), 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]);
}
}
}
/// @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 <typename BasicNodeType, typename T, std::size_t N0, std::size_t N1, std::size_t N2>
inline auto from_node(const BasicNodeType& n, T (&array)[N0][N1][N2])
-> decltype(n.get_value_inplace(std::declval<T&>()), 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++) {
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::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 <typename BasicNodeType, typename T, std::size_t N>
inline auto from_node(const BasicNodeType& n, std::array<T, N>& arr)
-> decltype(n.get_value_inplace(std::declval<T&>()), void()) {
if FK_YAML_UNLIKELY (!n.is_sequence()) {
throw type_error("The target node value type is not sequence type.", n.get_type());
}
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 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 <typename BasicNodeType, typename T>
inline auto from_node(const BasicNodeType& n, std::valarray<T>& va)
-> decltype(n.get_value_inplace(std::declval<T&>()), void()) {
if FK_YAML_UNLIKELY (!n.is_sequence()) {
throw type_error("The target node value type is not sequence type.", n.get_type());
}
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 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 <typename BasicNodeType, typename T, typename Alloc>
inline auto from_node(const BasicNodeType& n, std::forward_list<T, Alloc>& fl)
-> decltype(n.template get_value<T>(), void()) {
if FK_YAML_UNLIKELY (!n.is_sequence()) {
throw type_error("The target node value is not sequence type.", n.get_type());
}
fl.clear();
// 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<T>());
}
}
/// @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<BasicNodeType>, is_constructible_sequence_type<BasicNodeType, CompatSeqType>,
negation<std::is_constructible<typename BasicNodeType::string_type, CompatSeqType>>>::value,
int> = 0>
inline auto from_node(const BasicNodeType& n, CompatSeqType& s)
-> decltype(n.template get_value<typename CompatSeqType::value_type>(), void()) {
if FK_YAML_UNLIKELY (!n.is_sequence()) {
throw type_error("The target node value is not sequence type.", n.get_type());
}
s.clear();
// call reserve function first if it's available (like std::vector).
call_reserve_if_available<CompatSeqType>::call(s, n.size());
// 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<typename CompatSeqType::value_type>();
});
}
/// @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_basic_node<BasicNodeType>, is_sequence_container_adapter<SeqContainerAdapter>>::value, int> = 0>
inline auto from_node(const BasicNodeType& n, SeqContainerAdapter& ca)
-> decltype(n.template get_value<typename SeqContainerAdapter::value_type>(), ca.push(std::declval<typename SeqContainerAdapter::value_type>()), void()) {
if FK_YAML_UNLIKELY (!n.is_sequence()) {
throw type_error("The target node value is not sequence type.", n.get_type());
}
// clear existing elements manually since clear function is not implemented for container adapter classes.
while (!ca.empty()) {
ca.pop();
}
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<bool> in C++11.
ca.push(elem.template get_value<typename SeqContainerAdapter::value_type>());
}
}
/// @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 <typename BasicNodeType, typename CompatMapType, enable_if_t<is_constructible_mapping_type<CompatMapType>::value, int> = 0>
inline auto from_node(const BasicNodeType& n, CompatMapType& m)
-> decltype(
std::declval<const BasicNodeType&>().template get_value<typename CompatMapType::key_type>(),
std::declval<const BasicNodeType&>().template get_value<typename CompatMapType::mapped_type>(),
m.emplace(std::declval<typename CompatMapType::key_type>(), std::declval<typename CompatMapType::mapped_type>()),
void()) {
if FK_YAML_UNLIKELY (!n.is_mapping()) {
throw type_error("The target node value type is not mapping type.", n.get_type());
}
m.clear();
call_reserve_if_available<CompatMapType>::call(m, n.size());
for (const auto& pair : n.as_map()) {
m.emplace(
pair.first.template get_value<typename CompatMapType::key_type>(),
pair.second.template get_value<typename CompatMapType::mapped_type>());
}
}
/// @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 <typename BasicNodeType, enable_if_t<is_basic_node<BasicNodeType>::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;
}
/// @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 <typename BasicNodeType, enable_if_t<is_basic_node<BasicNodeType>::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<bool>(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<float_type>(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());
}
}
/// @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<typename BasicNodeType::integer_type, IntType>::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();
}
};
/// @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 <typename BasicNodeType, typename IntType>
struct from_node_int_helper<BasicNodeType, IntType, false> {
/// @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();
// under/overflow check.
if (std::is_same<IntType, uint64_t>::value) {
if FK_YAML_UNLIKELY (tmp_int < 0) {
throw exception("Integer value underflow detected.");
}
}
else {
if FK_YAML_UNLIKELY (tmp_int < static_cast<node_int_type>(std::numeric_limits<IntType>::min())) {
throw exception("Integer value underflow detected.");
}
if FK_YAML_UNLIKELY (static_cast<node_int_type>(std::numeric_limits<IntType>::max()) < tmp_int) {
throw exception("Integer value overflow detected.");
}
}
return static_cast<IntType>(tmp_int);
}
};
/// @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<conjunction<is_basic_node<BasicNodeType>, is_non_bool_integral<IntegerType>>::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<IntegerType>(0);
break;
case node_type::BOOLEAN:
i = static_cast<bool>(n.as_bool()) ? static_cast<IntegerType>(1) : static_cast<IntegerType>(0);
break;
case node_type::INTEGER:
i = from_node_int_helper<BasicNodeType, IntegerType>::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<int64_t>(n.as_float());
// under/overflow check.
if (std::is_same<IntegerType, uint64_t>::value) {
if FK_YAML_UNLIKELY (tmp_int < 0) {
throw exception("Integer value underflow detected.");
}
}
else {
if FK_YAML_UNLIKELY (tmp_int < static_cast<int64_t>(std::numeric_limits<IntegerType>::min())) {
throw exception("Integer value underflow detected.");
}
if FK_YAML_UNLIKELY (static_cast<int64_t>(std::numeric_limits<IntegerType>::max()) < tmp_int) {
throw exception("Integer value overflow detected.");
}
}
i = static_cast<IntegerType>(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 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<typename BasicNodeType::float_number_type, FloatType>::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 <typename BasicNodeType, typename FloatType>
struct from_node_float_helper<BasicNodeType, FloatType, false> {
/// @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<node_float_type>::infinity()) {
return std::numeric_limits<FloatType>::infinity();
}
return static_cast<FloatType>(-1.) * std::numeric_limits<FloatType>::infinity();
}
// check if the value is not a number
if (std::isnan(tmp_float)) {
return std::numeric_limits<FloatType>::quiet_NaN();
}
// check if the value is expressible as FloatType.
if FK_YAML_UNLIKELY (tmp_float < std::numeric_limits<FloatType>::lowest()) {
throw exception("Floating point value underflow detected.");
}
if FK_YAML_UNLIKELY (std::numeric_limits<FloatType>::max() < tmp_float) {
throw exception("Floating point value overflow detected.");
}
return static_cast<FloatType>(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.
/// @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<conjunction<is_basic_node<BasicNodeType>, std::is_floating_point<FloatType>>::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<FloatType>(0.);
break;
case node_type::BOOLEAN:
f = static_cast<bool>(n.as_bool()) ? static_cast<FloatType>(1.) : static_cast<FloatType>(0.);
break;
case node_type::INTEGER:
f = static_cast<FloatType>(n.as_int());
break;
case node_type::FLOAT:
f = from_node_float_helper<BasicNodeType, FloatType>::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 from_node function for BasicNodeType::string_type objects.
/// @tparam BasicNodeType A basic_node template instance type.
/// @param n A basic_node object.
/// @param s A string node value object.
template <typename BasicNodeType, enable_if_t<is_basic_node<BasicNodeType>::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 from_node function for compatible string type.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam CompatibleStringType A compatible string type.
/// @param n A basic_node object.
/// @param s A compatible string object.
template <
typename BasicNodeType, typename CompatibleStringType,
enable_if_t<
conjunction<
is_basic_node<BasicNodeType>,
negation<std::is_same<CompatibleStringType, typename BasicNodeType::string_type>>,
disjunction<
std::is_constructible<CompatibleStringType, const typename BasicNodeType::string_type&>,
std::is_assignable<CompatibleStringType, const typename BasicNodeType::string_type&>>>::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());
}
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.
/// @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.
/// @param n A basic_node object.
/// @param p A std::pair object.
template <typename BasicNodeType, typename T, typename U, enable_if_t<is_basic_node<BasicNodeType>::value, int> = 0>
inline auto from_node(const BasicNodeType& n, std::pair<T, U>& p)
-> decltype(std::declval<const BasicNodeType&>().template get_value<T>(), std::declval<const BasicNodeType&>().template get_value<U>(), 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 <typename BasicNodeType, typename... Types, std::size_t... Idx>
inline std::tuple<Types...> from_node_tuple_impl(const BasicNodeType& n, index_sequence<Idx...> /*unused*/) {
return std::make_tuple(n.at(Idx).template get_value<Types>()...);
}
/// @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 ...Types Value types of std::tuple.
/// @param n A basic_node object.
/// @param t A std::tuple object.
template <typename BasicNodeType, typename... Types, enable_if_t<is_basic_node<BasicNodeType>::value, int> = 0>
inline void from_node(const BasicNodeType& n, std::tuple<Types...>& t) {
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<BasicNodeType, Types...>(n, index_sequence_for<Types...> {});
}
#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 value type of the std::optional.
/// @param n A basic_node object.
/// @param o A std::optional object.
template <typename BasicNodeType, typename T, enable_if_t<is_basic_node<BasicNodeType>::value, int> = 0>
inline auto from_node(const BasicNodeType& n, std::optional<T>& o) -> decltype(n.template get_value<T>(), void()) {
try {
o.emplace(n.template get_value<T>());
}
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;
}
}
#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<T>(val))) void by default. User can set it to some other type.
template <typename BasicNodeType, typename T>
auto operator()(const BasicNodeType& n, T&& val) const
noexcept(noexcept(from_node(n, std::forward<T>(val)))) -> decltype(from_node(n, std::forward<T>(val))) {
return from_node(n, std::forward<T>(val));
}
};
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<detail::from_node_fn>::value;
#ifndef FK_YAML_HAS_CXX_17
} // namespace
#endif
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP */
// #include <fkYAML/detail/conversions/to_node.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP
#define FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP
#include <utility>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/exception_safe_allocation.hpp>
// #include <fkYAML/detail/meta/node_traits.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/detail/meta/stl_supplement.hpp>
// #include <fkYAML/detail/node_attrs.hpp>
// #include <fkYAML/node_type.hpp>
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 <typename BasicNodeType>
struct external_node_constructor {
template <typename... Args>
static void sequence(BasicNodeType& n, Args&&... args) {
destroy(n);
n.m_attrs |= node_attr_bits::seq_bit;
n.m_value.p_seq = create_object<typename BasicNodeType::sequence_type>(std::forward<Args>(args)...);
}
template <typename... Args>
static void mapping(BasicNodeType& n, Args&&... args) {
destroy(n);
n.m_attrs |= node_attr_bits::map_bit;
n.m_value.p_map = create_object<typename BasicNodeType::mapping_type>(std::forward<Args>(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;
}
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 <typename... Args>
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<typename BasicNodeType::string_type>(std::forward<Args>(args)...);
}
private:
static void destroy(BasicNodeType& n) {
n.m_value.destroy(n.m_attrs & node_attr_mask::value);
n.m_attrs &= ~node_attr_mask::value;
}
};
/////////////////
// 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<BasicNodeType>,
std::is_same<typename BasicNodeType::sequence_type, remove_cvref_t<T>>>::value,
int> = 0>
inline void to_node(BasicNodeType& n, T&& s) noexcept {
external_node_constructor<BasicNodeType>::sequence(n, std::forward<T>(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<BasicNodeType>,
negation<std::is_same<typename BasicNodeType::sequence_type, remove_cvref_t<CompatSeqType>>>,
negation<is_basic_node<CompatSeqType>>, detect::has_begin_end<CompatSeqType>,
negation<conjunction<detect::has_key_type<CompatSeqType>, detect::has_mapped_type<CompatSeqType>>>,
negation<std::is_constructible<typename BasicNodeType::string_type, CompatSeqType>>>::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<BasicNodeType>::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 <typename BasicNodeType, typename T, typename U>
inline void to_node(BasicNodeType& n, const std::pair<T, U>& 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 <typename BasicNodeType, typename... Types, std::size_t... Idx>
inline void to_node_tuple_impl(BasicNodeType& n, const std::tuple<Types...>& t, index_sequence<Idx...> /*unused*/) {
n = {std::get<Idx>(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 <typename BasicNodeType>
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 <typename BasicNodeType, typename FirstType, typename... RestTypes>
inline void to_node(BasicNodeType& n, const std::tuple<FirstType, RestTypes...>& t) {
to_node_tuple_impl(n, t, index_sequence_for<FirstType, RestTypes...> {});
}
/// @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<BasicNodeType>, std::is_same<typename BasicNodeType::mapping_type, remove_cvref_t<T>>>::value,
int> = 0>
inline void to_node(BasicNodeType& n, T&& m) noexcept {
external_node_constructor<BasicNodeType>::mapping(n, std::forward<T>(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<BasicNodeType>, negation<is_basic_node<CompatMapType>>,
negation<std::is_same<typename BasicNodeType::mapping_type, remove_cvref_t<CompatMapType>>>,
detect::has_begin_end<CompatMapType>, detect::has_key_type<CompatMapType>,
detect::has_mapped_type<CompatMapType>>::value,
int> = 0>
inline void to_node(BasicNodeType& n, CompatMapType&& m) {
external_node_constructor<BasicNodeType>::mapping(n);
auto& map = n.as_map();
for (const auto& pair : std::forward<CompatMapType>(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 <typename BasicNodeType, enable_if_t<is_basic_node<BasicNodeType>::value, int> = 0>
inline void to_node(BasicNodeType& n, std::nullptr_t /*unused*/) {
external_node_constructor<BasicNodeType>::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 <typename BasicNodeType, enable_if_t<is_basic_node<BasicNodeType>::value, int> = 0>
inline void to_node(BasicNodeType& n, typename BasicNodeType::boolean_type b) noexcept {
external_node_constructor<BasicNodeType>::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<conjunction<is_basic_node<BasicNodeType>, is_non_bool_integral<T>>::value, int> = 0>
inline void to_node(BasicNodeType& n, T i) noexcept {
external_node_constructor<BasicNodeType>::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<conjunction<is_basic_node<BasicNodeType>, std::is_floating_point<T>>::value, int> = 0>
inline void to_node(BasicNodeType& n, T f) noexcept {
external_node_constructor<BasicNodeType>::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<BasicNodeType>, negation<is_null_pointer<T>>,
std::is_constructible<typename BasicNodeType::string_type, T>>::value,
int> = 0>
inline void to_node(BasicNodeType& n, T&& s) {
external_node_constructor<BasicNodeType>::string_scalar(n, std::forward<T>(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<T>(val))) void by default. User can set it to some other type.
template <typename BasicNodeType, typename T>
auto operator()(BasicNodeType& n, T&& val) const
noexcept(noexcept(to_node(n, std::forward<T>(val)))) -> decltype(to_node(n, std::forward<T>(val))) {
return to_node(n, std::forward<T>(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<detail::to_node_fn>::value;
#ifndef FK_YAML_HAS_CXX_17
} // namespace
#endif
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP */
FK_YAML_NAMESPACE_BEGIN
/// @brief An ADL friendly converter between basic_node objects and native data objects.
/// @tparam ValueType A default target data type.
/// @sa https://fktn-k.github.io/fkYAML/api/node_value_converter/
template <typename ValueType, typename>
class node_value_converter {
public:
/// @brief Convert a YAML node value into compatible native data.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam TargetType A native data type for conversion.
/// @param n A basic_node object.
/// @param val A native data object.
/// @sa https://fktn-k.github.io/fkYAML/api/node_value_converter/from_node/
template <typename BasicNodeType, typename TargetType = ValueType>
static auto from_node(BasicNodeType&& n, TargetType& val) noexcept(
noexcept(::fkyaml::from_node(std::forward<BasicNodeType>(n), val)))
-> decltype(::fkyaml::from_node(std::forward<BasicNodeType>(n), val), void()) {
::fkyaml::from_node(std::forward<BasicNodeType>(n), val);
}
/// @brief Convert compatible native data into a YAML node.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam TargetType A native data type for conversion.
/// @param n A basic_node object.
/// @param val A native data object.
/// @sa https://fktn-k.github.io/fkYAML/api/node_value_converter/to_node/
template <typename BasicNodeType, typename TargetType = ValueType>
static auto to_node(BasicNodeType& n, TargetType&& val) noexcept(noexcept(::fkyaml::to_node(
n, std::forward<TargetType>(val)))) -> decltype(::fkyaml::to_node(n, std::forward<TargetType>(val))) {
::fkyaml::to_node(n, std::forward<TargetType>(val));
}
};
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_NODE_VALUE_CONVERTER_HPP */
// #include <fkYAML/ordered_map.hpp>
// _______ __ __ __ _____ __ __ __
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library
// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML
//
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
// SPDX-License-Identifier: MIT
#ifndef FK_YAML_ORDERED_MAP_HPP
#define FK_YAML_ORDERED_MAP_HPP
#include <functional>
#include <initializer_list>
#include <memory>
#include <utility>
#include <vector>
// #include <fkYAML/detail/macros/define_macros.hpp>
// #include <fkYAML/detail/meta/type_traits.hpp>
// #include <fkYAML/exception.hpp>
FK_YAML_NAMESPACE_BEGIN
/// @brief A minimal map-like container which preserves insertion order.
/// @tparam Key A type for keys.
/// @tparam Value A type for values.
/// @tparam IgnoredCompare A placeholder for key comparison. This will be ignored.
/// @tparam Allocator A class for allocators.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
template <
typename Key, typename Value, typename IgnoredCompare = std::less<Key>,
typename Allocator = std::allocator<std::pair<const Key, Value>>>
class ordered_map : public std::vector<std::pair<const Key, Value>, Allocator> {
public:
/// @brief A type for keys.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using key_type = Key;
/// @brief A type for values.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using mapped_type = Value;
/// @brief A type for internal key-value containers.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using Container = std::vector<std::pair<const Key, Value>, Allocator>;
/// @brief A type for key-value pairs.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using value_type = typename Container::value_type;
/// @brief A type for non-const iterators.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using iterator = typename Container::iterator;
/// @brief A type for const iterators.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using const_iterator = typename Container::const_iterator;
/// @brief A type for size parameters used in this class.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using size_type = typename Container::size_type;
/// @brief A type for comparison between keys.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/
using key_compare = std::equal_to<Key>;
public:
/// @brief Construct a new ordered_map object.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/constructor/
ordered_map() noexcept(noexcept(Container()))
: Container() {
}
/// @brief Construct a new ordered_map object with an initializer list.
/// @param init An initializer list to construct the inner container object.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/constructor/
ordered_map(std::initializer_list<value_type> init)
: Container {init} {
}
public:
/// @brief A subscript operator for ordered_map objects.
/// @tparam KeyType A type for the input key.
/// @param key A key to the target value.
/// @return mapped_type& Reference to a mapped_type object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/operator[]/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
mapped_type& operator[](KeyType&& key) noexcept {
return emplace(std::forward<KeyType>(key), mapped_type()).first->second;
}
public:
/// @brief Emplace a new key-value pair if the new key does not exist.
/// @tparam KeyType A type for the input key.
/// @param key A key to be emplaced to this ordered_map object.
/// @param value A value to be emplaced to this ordered_map object.
/// @return std::pair<iterator, bool> A result of emplacement of the new key-value pair.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/emplace/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
std::pair<iterator, bool> emplace(KeyType&& key, const mapped_type& value) noexcept {
for (auto itr = this->begin(); itr != this->end(); ++itr) {
if (m_compare(itr->first, key)) {
return {itr, false};
}
}
this->emplace_back(std::forward<KeyType>(key), value);
return {std::prev(this->end()), true};
}
/// @brief Find a value associated to the given key. Throws an exception if the search fails.
/// @tparam KeyType A type for the input key.
/// @param key A key to find a value with.
/// @return mapped_type& The value associated to the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/at/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
mapped_type& at(KeyType&& key) { // NOLINT(cppcoreguidelines-missing-std-forward)
for (auto itr = this->begin(); itr != this->end(); ++itr) {
if (m_compare(itr->first, key)) {
return itr->second;
}
}
throw fkyaml::exception("key not found.");
}
/// @brief Find a value associated to the given key. Throws an exception if the search fails.
/// @tparam KeyType A type for the input key.
/// @param key A key to find a value with.
/// @return const mapped_type& The value associated to the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/at/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const mapped_type& at(KeyType&& key) const { // NOLINT(cppcoreguidelines-missing-std-forward)
for (auto itr = this->begin(); itr != this->end(); ++itr) {
if (m_compare(itr->first, key)) {
return itr->second;
}
}
throw fkyaml::exception("key not found.");
}
/// @brief Find a value with the given key.
/// @tparam KeyType A type for the input key.
/// @param key A key to find a value with.
/// @return iterator The iterator for the found value, or the result of end().
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/find/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
iterator find(KeyType&& key) noexcept { // NOLINT(cppcoreguidelines-missing-std-forward)
for (auto itr = this->begin(); itr != this->end(); ++itr) {
if (m_compare(itr->first, key)) {
return itr;
}
}
return this->end();
}
/// @brief Find a value with the given key.
/// @tparam KeyType A type for the input key.
/// @param key A key to find a value with.
/// @return const_iterator The constant iterator for the found value, or the result of end().
/// @sa https://fktn-k.github.io/fkYAML/api/ordered_map/find/
template <
typename KeyType,
detail::enable_if_t<detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const_iterator find(KeyType&& key) const noexcept { // NOLINT(cppcoreguidelines-missing-std-forward)
for (auto itr = this->begin(); itr != this->end(); ++itr) {
if (m_compare(itr->first, key)) {
return itr;
}
}
return this->end();
}
private:
/// The object for comparing keys.
key_compare m_compare {};
};
FK_YAML_NAMESPACE_END
#endif /* FK_YAML_ORDERED_MAP_HPP */
FK_YAML_NAMESPACE_BEGIN
/// @brief A class to store value of YAML nodes.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename = void> class ConverterType>
class basic_node {
public:
/// @brief A type for sequence basic_node values.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/sequence_type/
using sequence_type = SequenceType<basic_node, std::allocator<basic_node>>;
/// @brief A type for mapping basic_node values.
/// @note std::unordered_map is not supported since it does not allow incomplete types.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/mapping_type/
using mapping_type = MappingType<basic_node, basic_node>;
/// @brief A type for boolean basic_node values.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/boolean_type/
using boolean_type = BooleanType;
/// @brief A type for integer basic_node values.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/integer_type/
using integer_type = IntegerType;
/// @brief A type for float number basic_node values.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/float_number_type/
using float_number_type = FloatNumberType;
/// @brief A type for string basic_node values.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/string_type/
using string_type = StringType;
/// @brief A type of elements in a basic_node container.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using value_type = basic_node;
/// @brief A type of reference to a basic_node element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using reference = value_type&;
/// @brief A type of constant reference to a basic_node element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using const_reference = const value_type&;
/// @brief A type of a pointer to a basic_node element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using pointer = value_type*;
/// @brief A type of a constant pointer to a basic_node element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using const_pointer = const value_type*;
/// @brief A type to represent basic_node container sizes.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using size_type = std::size_t;
/// @brief A type to represent differences between basic_node iterators.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/#container-types
using difference_type = std::ptrdiff_t;
/// @brief A type for iterators of basic_node containers.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/iterator/
using iterator = fkyaml::detail::iterator<basic_node>;
/// @brief A type for constant iterators of basic_node containers.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/iterator/
using const_iterator = fkyaml::detail::iterator<const basic_node>;
/// @brief A type for reverse iterators of basic_node containers.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/reverse_iterator/
using reverse_iterator = fkyaml::detail::reverse_iterator<iterator>;
/// @brief A type for constant reverse iterators of basic_node containers.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/reverse_iterator/
using const_reverse_iterator = fkyaml::detail::reverse_iterator<const_iterator>;
/// @brief A helper alias to determine converter type for the given target native data type.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/value_converter_type/
template <typename T, typename SFINAE>
using value_converter_type = ConverterType<T, SFINAE>;
/// @brief Definition of node value types.
/// @deprecated Use fkyaml::node_type enum class. (since 0.3.12)
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/node_t/
using node_t = detail::node_t;
/// @brief Definition of YAML version types.
/// @deprecated Use fkyaml::yaml_version_type enum class. (since 0.3.12)
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/yaml_version_t/
using yaml_version_t = detail::yaml_version_t;
/// @brief A type for mapping range objects for the map_items() function.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/map_range/
using map_range = fkyaml::detail::map_range_proxy<basic_node>;
/// @brief A type for constant mapping range objects for the map_items() function.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/map_range/
using const_map_range = fkyaml::detail::map_range_proxy<const basic_node>;
private:
template <typename BasicNodeType>
friend struct fkyaml::detail::external_node_constructor;
template <typename BasicNodeType>
friend class fkyaml::detail::basic_deserializer;
template <typename BasicNodeType>
friend class fkyaml::detail::basic_serializer;
/// @brief A type for YAML docs deserializers.
using deserializer_type = detail::basic_deserializer<basic_node>;
/// @brief A type for YAML docs serializers.
using serializer_type = detail::basic_serializer<basic_node>;
/// @brief A helper type alias for std::initializer_list.
using initializer_list_t = std::initializer_list<detail::node_ref_storage<basic_node>>;
/// @brief The actual storage for a YAML node value of the @ref basic_node class.
/// @details This union combines the different storage types for the YAML value types defined in @ref node_t.
/// @note Container types are stored as pointers so that the size of this union will not exceed 64 bits by
/// default.
union node_value {
/// @brief Constructs a new basic_node Value object for null types.
node_value() = default;
/// @brief Constructs a new basic_node value object with a node type. The default value for the specified
/// type will be assigned.
/// @param[in] type A node type.
explicit node_value(detail::node_attr_t value_type_bit) {
switch (value_type_bit) {
case detail::node_attr_bits::seq_bit:
p_seq = detail::create_object<sequence_type>();
break;
case detail::node_attr_bits::map_bit:
p_map = detail::create_object<mapping_type>();
break;
case detail::node_attr_bits::null_bit:
p_map = nullptr;
break;
case detail::node_attr_bits::bool_bit:
boolean = static_cast<boolean_type>(false);
break;
case detail::node_attr_bits::int_bit:
integer = static_cast<integer_type>(0);
break;
case detail::node_attr_bits::float_bit:
float_val = static_cast<float_number_type>(0.0);
break;
case detail::node_attr_bits::string_bit:
p_str = detail::create_object<string_type>();
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
/// @brief Destroys the existing Node value. This process is recursive if the specified node type is for
/// containers.
/// @param[in] type A Node type to determine the value to be destroyed.
void destroy(detail::node_attr_t value_type_bit) {
switch (value_type_bit) {
case detail::node_attr_bits::seq_bit:
p_seq->clear();
detail::destroy_object<sequence_type>(p_seq);
p_seq = nullptr;
break;
case detail::node_attr_bits::map_bit:
p_map->clear();
detail::destroy_object<mapping_type>(p_map);
p_map = nullptr;
break;
case detail::node_attr_bits::string_bit:
detail::destroy_object<string_type>(p_str);
p_str = nullptr;
break;
default:
break;
}
}
/// A pointer to the value of sequence type.
sequence_type* p_seq;
/// A pointer to the value of mapping type. This pointer is also used when node type is null.
mapping_type* p_map {nullptr};
/// A value of boolean type.
boolean_type boolean;
/// A value of integer type.
integer_type integer;
/// A value of float number type.
float_number_type float_val;
/// A pointer to the value of string type.
string_type* p_str;
};
public:
/// @brief Constructs a new basic_node object of null type.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
basic_node() = default;
/// @brief Constructs a new basic_node object with a specified type.
/// @param[in] type A YAML node type.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
FK_YAML_DEPRECATED("Since 0.3.12; Use explicit basic_node(const node_type)")
explicit basic_node(const node_t type)
: basic_node(detail::convert_to_node_type(type)) {
}
explicit basic_node(const node_type type)
: m_attrs(detail::node_attr_bits::from_node_type(type)),
m_value(m_attrs & detail::node_attr_mask::value) {
}
/// @brief Copy constructor of the basic_node class.
/// @param[in] rhs A basic_node object to be copied with.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
basic_node(const basic_node& rhs)
: m_attrs(rhs.m_attrs),
mp_meta(rhs.mp_meta),
m_prop(rhs.m_prop) {
if FK_YAML_LIKELY (!has_anchor_name()) {
switch (m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
m_value.p_seq = detail::create_object<sequence_type>(*(rhs.m_value.p_seq));
break;
case detail::node_attr_bits::map_bit:
m_value.p_map = detail::create_object<mapping_type>(*(rhs.m_value.p_map));
break;
case detail::node_attr_bits::null_bit:
m_value.p_map = nullptr;
break;
case detail::node_attr_bits::bool_bit:
m_value.boolean = rhs.m_value.boolean;
break;
case detail::node_attr_bits::int_bit:
m_value.integer = rhs.m_value.integer;
break;
case detail::node_attr_bits::float_bit:
m_value.float_val = rhs.m_value.float_val;
break;
case detail::node_attr_bits::string_bit:
m_value.p_str = detail::create_object<string_type>(*(rhs.m_value.p_str));
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
}
/// @brief Move constructor of the basic_node class.
/// @param[in] rhs A basic_node object to be moved from.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
basic_node(basic_node&& rhs) noexcept
: m_attrs(rhs.m_attrs),
mp_meta(std::move(rhs.mp_meta)),
m_prop(std::move(rhs.m_prop)) {
if FK_YAML_LIKELY (!has_anchor_name()) {
switch (m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(rhs.m_value.p_seq != nullptr);
m_value.p_seq = rhs.m_value.p_seq;
rhs.m_value.p_seq = nullptr;
break;
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(rhs.m_value.p_map != nullptr);
m_value.p_map = rhs.m_value.p_map;
rhs.m_value.p_map = nullptr;
break;
case detail::node_attr_bits::null_bit:
FK_YAML_ASSERT(rhs.m_value.p_map == nullptr);
m_value.p_map = rhs.m_value.p_map;
break;
case detail::node_attr_bits::bool_bit:
m_value.boolean = rhs.m_value.boolean;
rhs.m_value.boolean = static_cast<boolean_type>(false);
break;
case detail::node_attr_bits::int_bit:
m_value.integer = rhs.m_value.integer;
rhs.m_value.integer = static_cast<integer_type>(0);
break;
case detail::node_attr_bits::float_bit:
m_value.float_val = rhs.m_value.float_val;
rhs.m_value.float_val = static_cast<float_number_type>(0.0);
break;
case detail::node_attr_bits::string_bit:
FK_YAML_ASSERT(rhs.m_value.p_str != nullptr);
m_value.p_str = rhs.m_value.p_str;
rhs.m_value.p_str = nullptr;
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
}
rhs.m_attrs = detail::node_attr_bits::default_bits;
rhs.m_value.p_map = nullptr;
}
/// @brief Construct a new basic_node object from a value of compatible types.
/// @tparam CompatibleType Type of native data which is compatible with node values.
/// @tparam U Type of compatible native data without cv-qualifiers and reference.
/// @param[in] val The value of a compatible type.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
template <
typename CompatibleType, typename U = detail::remove_cvref_t<CompatibleType>,
detail::enable_if_t<
detail::conjunction<
detail::negation<detail::is_basic_node<U>>,
detail::disjunction<detail::is_node_compatible_type<basic_node, U>>>::value,
int> = 0>
basic_node(CompatibleType&& val) noexcept(
noexcept(ConverterType<U, void>::to_node(std::declval<basic_node&>(), std::declval<CompatibleType>()))) {
ConverterType<U, void>::to_node(*this, std::forward<CompatibleType>(val));
}
/// @brief Construct a new basic node object with a node_ref_storage object.
/// @tparam NodeRefStorageType Type of basic_node with reference.
/// @param[in] node_ref_storage A node_ref_storage template class object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
template <
typename NodeRefStorageType,
detail::enable_if_t<detail::is_node_ref_storage<NodeRefStorageType>::value, int> = 0>
basic_node(const NodeRefStorageType& node_ref_storage) noexcept
: basic_node(node_ref_storage.release()) {
}
/// @brief Construct a new basic node object with std::initializer_list.
/// @param[in] init A initializer list of basic_node objects.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/constructor/
basic_node(initializer_list_t init) {
bool is_mapping =
std::all_of(init.begin(), init.end(), [](const detail::node_ref_storage<basic_node>& node_ref) {
// Do not use is_sequence_impl() since node_ref may be an anchor or alias.
return node_ref->is_sequence() && node_ref->size() == 2;
});
if (is_mapping) {
m_attrs = detail::node_attr_bits::map_bit;
m_value.p_map = detail::create_object<mapping_type>();
auto& map = *m_value.p_map;
for (auto& elem_ref : init) {
auto elem = elem_ref.release();
auto& seq = *elem.m_value.p_seq;
map.emplace(std::move(seq[0]), std::move(seq[1]));
}
}
else {
m_attrs = detail::node_attr_bits::seq_bit;
m_value.p_seq = detail::create_object<sequence_type>();
auto& seq = *m_value.p_seq;
seq.reserve(std::distance(init.begin(), init.end()));
for (auto& elem_ref : init) {
seq.emplace_back(std::move(elem_ref.release()));
}
}
}
/// @brief Destroy the basic_node object and its value storage.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/destructor/
~basic_node() noexcept // NOLINT(bugprone-exception-escape)
{
if (m_attrs & detail::node_attr_mask::anchoring) {
if (m_attrs & detail::node_attr_bits::anchor_bit) {
auto itr = mp_meta->anchor_table.equal_range(m_prop.anchor).first;
std::advance(itr, detail::node_attr_bits::get_anchor_offset(m_attrs));
itr->second.m_value.destroy(itr->second.m_attrs & detail::node_attr_mask::value);
itr->second.m_attrs = detail::node_attr_bits::default_bits;
itr->second.mp_meta.reset();
}
}
else if ((m_attrs & detail::node_attr_bits::null_bit) == 0) {
m_value.destroy(m_attrs & detail::node_attr_mask::value);
}
m_attrs = detail::node_attr_bits::default_bits;
mp_meta.reset();
}
public:
/// @brief Deserialize the first YAML document in the input into a basic_node object.
/// @tparam InputType Type of a compatible input.
/// @param[in] input An input source in the YAML format.
/// @return The resulting basic_node object deserialized from the input source.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/deserialize/
template <typename InputType>
static basic_node deserialize(InputType&& input) {
return deserializer_type().deserialize(detail::input_adapter(std::forward<InputType>(input)));
}
/// @brief Deserialize the first YAML document in the input ranged by the iterators into a basic_node object.
/// @note
/// Iterators must satisfy the LegacyInputIterator requirements.
/// See https://en.cppreference.com/w/cpp/named_req/InputIterator.
/// @tparam ItrType Type of a compatible iterator
/// @param[in] begin An iterator to the first element of an input sequence.
/// @param[in] end An iterator to the past-the-last element of an input sequence.
/// @return The resulting basic_node object deserialized from the pair of iterators.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/deserialize/
template <typename ItrType>
static basic_node deserialize(ItrType begin, ItrType end) {
return deserializer_type().deserialize(
detail::input_adapter(std::forward<ItrType>(begin), std::forward<ItrType>(end)));
}
/// @brief Deserialize all YAML documents in the input into basic_node objects.
/// @tparam InputType Type of a compatible input.
/// @param[in] input An input source in the YAML format.
/// @return The resulting basic_node objects deserialized from the input.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/deserialize_docs/
template <typename InputType>
static std::vector<basic_node> deserialize_docs(InputType&& input) {
return deserializer_type().deserialize_docs(detail::input_adapter(std::forward<InputType>(input)));
}
/// @brief Deserialize all YAML documents in the input ranged by the iterators into basic_node objects.
/// @tparam ItrType Type of a compatible iterator.
/// @param[in] begin An iterator to the first element of an input sequence.
/// @param[in] end An iterator to the past-the-last element of an input sequence.
/// @return The resulting basic_node objects deserialized from the pair of iterators.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/deserialize_docs/
template <typename ItrType>
static std::vector<basic_node> deserialize_docs(ItrType&& begin, ItrType&& end) {
return deserializer_type().deserialize_docs(
detail::input_adapter(std::forward<ItrType>(begin), std::forward<ItrType>(end)));
}
/// @brief Serialize a basic_node object into a string.
/// @param[in] node A basic_node object to be serialized.
/// @return The resulting string object from the serialization of the given node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/serialize/
static std::string serialize(const basic_node& node) {
return serializer_type().serialize(node);
}
/// @brief Serialize basic_node objects into a string.
/// @param docs basic_node objects to be serialized.
/// @return The resulting string object from the serialization of the given nodes.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/serialize_docs/
static std::string serialize_docs(const std::vector<basic_node>& docs) {
return serializer_type().serialize_docs(docs);
}
/// @brief A factory method for sequence basic_node objects without sequence_type objects.
/// @return A YAML sequence node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/sequence/
static basic_node sequence() {
basic_node node;
node.m_attrs = detail::node_attr_bits::seq_bit;
node.m_value.p_seq = detail::create_object<sequence_type>();
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for sequence basic_node objects with lvalue sequence_type objects.
/// @param[in] seq A lvalue sequence node value.
/// @return A YAML sequence node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/sequence/
static basic_node sequence(const sequence_type& seq) {
basic_node node;
node.m_attrs = detail::node_attr_bits::seq_bit;
node.m_value.p_seq = detail::create_object<sequence_type>(seq);
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for sequence basic_node objects with rvalue sequence_type objects.
/// @param[in] seq A rvalue sequence node value.
/// @return A YAML sequence node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/sequence/
static basic_node sequence(sequence_type&& seq) {
basic_node node;
node.m_attrs = detail::node_attr_bits::seq_bit;
node.m_value.p_seq = detail::create_object<sequence_type>(std::move(seq));
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for mapping basic_node objects without mapping_type objects.
/// @return A YAML mapping node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/mapping/
static basic_node mapping() {
basic_node node;
node.m_attrs = detail::node_attr_bits::map_bit;
node.m_value.p_map = detail::create_object<mapping_type>();
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for mapping basic_node objects with lvalue mapping_type objects.
/// @param[in] map A lvalue mapping node value.
/// @return A YAML mapping node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/mapping/
static basic_node mapping(const mapping_type& map) {
basic_node node;
node.m_attrs = detail::node_attr_bits::map_bit;
node.m_value.p_map = detail::create_object<mapping_type>(map);
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for mapping basic_node objects with rvalue mapping_type objects.
/// @param[in] map A rvalue mapping node value.
/// @return A YAML mapping node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/mapping/
static basic_node mapping(mapping_type&& map) {
basic_node node;
node.m_attrs = detail::node_attr_bits::map_bit;
node.m_value.p_map = detail::create_object<mapping_type>(std::move(map));
return node;
} // LCOV_EXCL_LINE
/// @brief A factory method for alias basic_node objects referencing the given anchor basic_node object.
/// @note The given anchor basic_node must have a non-empty anchor name.
/// @param[in] anchor_node A basic_node object with an anchor name.
/// @return An alias YAML node created from the given anchor node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/alias_of/
static basic_node alias_of(const basic_node& anchor_node) {
constexpr detail::node_attr_t anchor_bit = detail::node_attr_bits::anchor_bit;
if FK_YAML_UNLIKELY (!anchor_node.has_anchor_name() || !(anchor_node.m_attrs & anchor_bit)) {
throw fkyaml::exception("Cannot create an alias without anchor name.");
}
basic_node node = anchor_node;
node.m_attrs &= ~detail::node_attr_mask::anchoring;
node.m_attrs |= detail::node_attr_bits::alias_bit;
return node;
} // LCOV_EXCL_LINE
public:
/// @brief A copy assignment operator of the basic_node class.
/// @param[in] rhs A lvalue basic_node object to be copied with.
/// @return Reference to this basic_node object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator=/
basic_node& operator=(const basic_node& rhs) noexcept {
basic_node(rhs).swap(*this);
return *this;
}
/// @brief A move assignment operator of the basic_node class.
/// @param[in] rhs A rvalue basic_node object to be moved from.
/// @return Reference to this basic_node object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator=/
basic_node& operator=(basic_node&& rhs) noexcept {
basic_node(std::move(rhs)).swap(*this);
return *this;
}
/// @brief A subscript operator of the basic_node class with a key of a compatible type with basic_node.
/// @tparam KeyType A key type compatible with basic_node
/// @param key A key to the target value in a sequence/mapping node.
/// @return The value associated with the given key, or a default basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator[]/
template <
typename KeyType, detail::enable_if_t<
detail::conjunction<
detail::negation<detail::is_basic_node<KeyType>>,
detail::is_node_compatible_type<basic_node, KeyType>>::value,
int> = 0>
basic_node& operator[](KeyType&& key) {
basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("operator[] is unavailable for a scalar node.", get_type());
}
basic_node key_node = std::forward<KeyType>(key);
if (act_node.is_sequence_impl()) {
// Do not use is_integer_impl() since n may be an anchor or alias.
if FK_YAML_UNLIKELY (!key_node.is_integer()) {
throw fkyaml::type_error(
"An argument of operator[] for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return act_node.m_value.p_seq->operator[](key_node.get_value<int>());
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return act_node.m_value.p_map->operator[](std::move(key_node));
}
/// @brief A subscript operator of the basic_node class with a key of a compatible type with basic_node.
/// @tparam KeyType A key type compatible with basic_node
/// @param key A key to the target value in a sequence/mapping node.
/// @return The value associated with the given key, or a default basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator[]/
template <
typename KeyType, detail::enable_if_t<
detail::conjunction<
detail::negation<detail::is_basic_node<KeyType>>,
detail::is_node_compatible_type<basic_node, KeyType>>::value,
int> = 0>
const basic_node& operator[](KeyType&& key) const {
const basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("operator[] is unavailable for a scalar node.", get_type());
}
basic_node key_node = std::forward<KeyType>(key);
if (act_node.is_sequence_impl()) {
if FK_YAML_UNLIKELY (!key_node.is_integer_impl()) {
throw fkyaml::type_error(
"An argument of operator[] for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return act_node.m_value.p_seq->operator[](key_node.get_value<int>());
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return act_node.m_value.p_map->operator[](std::move(key_node));
}
/// @brief A subscript operator of the basic_node class with a basic_node key object.
/// @tparam KeyType A key type which is a kind of the basic_node template class.
/// @param key A key to the target value in a sequence/mapping node.
/// @return The value associated with the given key, or a default basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator[]/
template <typename KeyType, detail::enable_if_t<detail::is_basic_node<KeyType>::value, int> = 0>
basic_node& operator[](KeyType&& key) {
if FK_YAML_UNLIKELY (is_scalar()) {
throw fkyaml::type_error("operator[] is unavailable for a scalar node.", get_type());
}
const node_value& node_value = resolve_reference().m_value;
if (is_sequence()) {
if FK_YAML_UNLIKELY (!key.is_integer()) {
throw fkyaml::type_error(
"An argument of operator[] for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(node_value.p_seq != nullptr);
return node_value.p_seq->operator[](std::forward<KeyType>(key).template get_value<int>());
}
FK_YAML_ASSERT(node_value.p_map != nullptr);
return node_value.p_map->operator[](std::forward<KeyType>(key));
}
/// @brief A subscript operator of the basic_node class with a basic_node key object.
/// @tparam KeyType A key type which is a kind of the basic_node template class.
/// @param key A key to the target value in a sequence/mapping node.
/// @return The value associated with the given key, or a default basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator[]/
template <typename KeyType, detail::enable_if_t<detail::is_basic_node<KeyType>::value, int> = 0>
const basic_node& operator[](KeyType&& key) const {
if FK_YAML_UNLIKELY (is_scalar()) {
throw fkyaml::type_error("operator[] is unavailable for a scalar node.", get_type());
}
const node_value& node_value = resolve_reference().m_value;
if (is_sequence()) {
if FK_YAML_UNLIKELY (!key.is_integer()) {
throw fkyaml::type_error(
"An argument of operator[] for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(node_value.p_seq != nullptr);
return node_value.p_seq->operator[](key.template get_value<int>());
}
FK_YAML_ASSERT(node_value.p_map != nullptr);
return node_value.p_map->operator[](std::forward<KeyType>(key));
}
/// @brief An equal-to operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true if both types and values are equal, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_eq/
bool operator==(const basic_node& rhs) const noexcept {
const basic_node& lhs = resolve_reference();
const basic_node& act_rhs = rhs.resolve_reference();
const detail::node_attr_t lhs_val_bit = lhs.m_attrs & detail::node_attr_mask::value;
if (lhs_val_bit != (act_rhs.m_attrs & detail::node_attr_mask::value)) {
return false;
}
bool ret = false;
switch (lhs_val_bit) {
case detail::node_attr_bits::seq_bit:
ret = (*(lhs.m_value.p_seq) == *(act_rhs.m_value.p_seq));
break;
case detail::node_attr_bits::map_bit:
ret = (*(lhs.m_value.p_map) == *(act_rhs.m_value.p_map));
break;
case detail::node_attr_bits::null_bit:
// Always true for comparisons between null nodes.
ret = true;
break;
case detail::node_attr_bits::bool_bit:
ret = (lhs.m_value.boolean == act_rhs.m_value.boolean);
break;
case detail::node_attr_bits::int_bit:
ret = (lhs.m_value.integer == act_rhs.m_value.integer);
break;
case detail::node_attr_bits::float_bit:
ret =
(std::abs(lhs.m_value.float_val - act_rhs.m_value.float_val) <
std::numeric_limits<float_number_type>::epsilon());
break;
case detail::node_attr_bits::string_bit:
ret = (*(lhs.m_value.p_str) == *(act_rhs.m_value.p_str));
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
return ret;
}
/// @brief A not-equal-to operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true if either types or values are different, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_ne/
bool operator!=(const basic_node& rhs) const noexcept {
return !operator==(rhs);
}
/// @brief A less-than operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true this basic_node object is less than `rhs`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_lt/
bool operator<(const basic_node& rhs) const noexcept {
if (operator==(rhs)) {
return false;
}
const basic_node& lhs = resolve_reference();
const basic_node& act_rhs = rhs.resolve_reference();
const detail::node_attr_t lhs_val_bit = lhs.m_attrs & detail::node_attr_mask::value;
const detail::node_attr_t rhs_val_bit = act_rhs.m_attrs & detail::node_attr_mask::value;
if (lhs_val_bit < rhs_val_bit) {
return true;
}
if (lhs_val_bit != rhs_val_bit) {
return false;
}
bool ret = false;
switch (lhs_val_bit) {
case detail::node_attr_bits::seq_bit:
ret = (*(lhs.m_value.p_seq) < *(act_rhs.m_value.p_seq));
break;
case detail::node_attr_bits::map_bit:
ret = (*(lhs.m_value.p_map) < *(act_rhs.m_value.p_map));
break;
case detail::node_attr_bits::null_bit: // LCOV_EXCL_LINE
// Will not come here since null nodes are always the same.
detail::unreachable(); // LCOV_EXCL_LINE
case detail::node_attr_bits::bool_bit:
// false < true
ret = (!lhs.m_value.boolean && act_rhs.m_value.boolean);
break;
case detail::node_attr_bits::int_bit:
ret = (lhs.m_value.integer < act_rhs.m_value.integer);
break;
case detail::node_attr_bits::float_bit:
ret = (lhs.m_value.float_val < act_rhs.m_value.float_val);
break;
case detail::node_attr_bits::string_bit:
ret = (*(lhs.m_value.p_str) < *(act_rhs.m_value.p_str));
break;
default: // LCOV_EXCL_LINE
detail::unreachable(); // LCOV_EXCL_LINE
}
return ret;
}
/// @brief A less-than-or-equal-to operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true this basic_node object is less than or equal to `rhs`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_le/
bool operator<=(const basic_node& rhs) const noexcept {
return !rhs.operator<(*this);
}
/// @brief A greater-than operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true this basic_node object is greater than `rhs`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_gt/
bool operator>(const basic_node& rhs) const noexcept {
return !operator<=(rhs);
}
/// @brief A greater-than-or-equal-to operator of the basic_node class.
/// @param rhs A basic_node object to be compared with this basic_node object.
/// @return true this basic_node object is greater than or equal to `rhs`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/operator_ge/
bool operator>=(const basic_node& rhs) const noexcept {
return !operator<(rhs);
}
public:
/// @brief Returns the type of the current basic_node value.
/// @return The type of the YAML node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_type/
node_type get_type() const noexcept {
return detail::node_attr_bits::to_node_type(resolve_reference().m_attrs);
}
/// @brief Returns the type of the current basic_node value.
/// @deprecated Use get_type() function. (since 0.3.12)
/// @return The type of the YAML node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/type/
FK_YAML_DEPRECATED("Since 0.3.12; Use get_type()")
node_t type() const noexcept {
node_type tmp_type = get_type();
return detail::convert_from_node_type(tmp_type);
}
/// @brief Tests whether the current basic_node value is of sequence type.
/// @return true if the type is sequence, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_sequence/
bool is_sequence() const noexcept {
return resolve_reference().is_sequence_impl();
}
/// @brief Tests whether the current basic_node value is of mapping type.
/// @return true if the type is mapping, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_mapping/
bool is_mapping() const noexcept {
return resolve_reference().is_mapping_impl();
}
/// @brief Tests whether the current basic_node value is of null type.
/// @return true if the type is null, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_null/
bool is_null() const noexcept {
return resolve_reference().is_null_impl();
}
/// @brief Tests whether the current basic_node value is of boolean type.
/// @return true if the type is boolean, false otherwise
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_boolean/
bool is_boolean() const noexcept {
return resolve_reference().is_boolean_impl();
}
/// @brief Tests whether the current basic_node value is of integer type.
/// @return true if the type is integer, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_integer/
bool is_integer() const noexcept {
return resolve_reference().is_integer_impl();
}
/// @brief Tests whether the current basic_node value is of float number type.
/// @return true if the type is floating point number, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_float_number/
bool is_float_number() const noexcept {
return resolve_reference().is_float_number_impl();
}
/// @brief Tests whether the current basic_node value is of string type.
/// @return true if the type is string, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_string/
bool is_string() const noexcept {
return resolve_reference().is_string_impl();
}
/// @brief Tests whether the current basic_node value is of scalar types.
/// @return true if the type is scalar, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_scalar/
bool is_scalar() const noexcept {
return resolve_reference().is_scalar_impl();
}
/// @brief Tests whether the current basic_node is an anchor node.
/// @return true if the current basic_node is an anchor node, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_anchor/
bool is_anchor() const noexcept {
return m_attrs & detail::node_attr_bits::anchor_bit;
}
/// @brief Tests whether the current basic_node is an alias node.
/// @return true if the current basic_node is an alias node, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/is_alias/
bool is_alias() const noexcept {
return m_attrs & detail::node_attr_bits::alias_bit;
}
/// @brief Tests whether the current basic_node value (sequence, mapping, string) is empty.
/// @return true if the node value is empty, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/empty/
bool empty() const {
const basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit: {
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return act_node.m_value.p_seq->empty();
}
case detail::node_attr_bits::map_bit: {
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return act_node.m_value.p_map->empty();
}
case detail::node_attr_bits::string_bit: {
FK_YAML_ASSERT(act_node.m_value.p_str != nullptr);
return act_node.m_value.p_str->empty();
}
default:
throw fkyaml::type_error("The target node is not of a container type.", get_type());
}
}
/// @brief Returns the size of the current basic_node value (sequence, mapping, string).
/// @return The size of a node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/size/
std::size_t size() const {
const basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return act_node.m_value.p_seq->size();
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return act_node.m_value.p_map->size();
case detail::node_attr_bits::string_bit:
FK_YAML_ASSERT(act_node.m_value.p_str != nullptr);
return act_node.m_value.p_str->size();
default:
throw fkyaml::type_error("The target node is not of a container type.", get_type());
}
}
/// @brief Check whether this basic_node object has a given key in its inner mapping node value.
/// @tparam KeyType A key type compatible with basic_node.
/// @param key A key to the target value in the mapping node value.
/// @return true if the target node is a mapping and has the given key, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/contains/
template <
typename KeyType, detail::enable_if_t<
detail::disjunction<
detail::is_basic_node<KeyType>,
detail::is_node_compatible_type<basic_node, detail::remove_cvref_t<KeyType>>>::value,
int> = 0>
bool contains(KeyType&& key) const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.m_attrs & detail::node_attr_bits::map_bit) {
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
const auto& map = *act_node.m_value.p_map;
return map.find(std::forward<KeyType>(key)) != map.end();
}
return false;
}
/// @brief Get a basic_node object with a key of a compatible type.
/// @tparam KeyType A key type compatible with basic_node
/// @param key A key to the target basic_node object in a sequence/mapping node.
/// @return Reference to the basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/at/
template <
typename KeyType, detail::enable_if_t<
detail::conjunction<
detail::negation<detail::is_basic_node<KeyType>>,
detail::is_node_compatible_type<basic_node, KeyType>>::value,
int> = 0>
basic_node& at(KeyType&& key) {
basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("at() is unavailable for a scalar node.", get_type());
}
basic_node node_key = std::forward<KeyType>(key);
if (act_node.is_sequence_impl()) {
if FK_YAML_UNLIKELY (!node_key.is_integer_impl()) {
throw fkyaml::type_error("An argument of at() for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
sequence_type& seq = *act_node.m_value.p_seq;
int index = std::move(node_key).template get_value<int>();
int size = static_cast<int>(seq.size());
if FK_YAML_UNLIKELY (index >= size) {
throw fkyaml::out_of_range(index);
}
return seq[index];
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
mapping_type& map = *act_node.m_value.p_map;
const bool is_found = map.find(node_key) != map.end();
if FK_YAML_UNLIKELY (!is_found) {
throw fkyaml::out_of_range(serialize(node_key).c_str());
}
return map[std::move(node_key)];
}
/// @brief Get a basic_node object with a key of a compatible type.
/// @tparam KeyType A key type compatible with basic_node
/// @param key A key to the target basic_node object in a sequence/mapping node.
/// @return Constant reference to the basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/at/
template <
typename KeyType, detail::enable_if_t<
detail::conjunction<
detail::negation<detail::is_basic_node<KeyType>>,
detail::is_node_compatible_type<basic_node, KeyType>>::value,
int> = 0>
const basic_node& at(KeyType&& key) const {
const basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("at() is unavailable for a scalar node.", get_type());
}
basic_node node_key = std::forward<KeyType>(key);
if (act_node.is_sequence_impl()) {
if FK_YAML_UNLIKELY (!node_key.is_integer()) {
throw fkyaml::type_error("An argument of at() for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
const sequence_type& seq = *act_node.m_value.p_seq;
int index = std::move(node_key).template get_value<int>();
int size = static_cast<int>(seq.size());
if FK_YAML_UNLIKELY (index >= size) {
throw fkyaml::out_of_range(index);
}
return seq[index];
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
const mapping_type& map = *act_node.m_value.p_map;
const bool is_found = map.find(node_key) != map.end();
if FK_YAML_UNLIKELY (!is_found) {
throw fkyaml::out_of_range(serialize(node_key).c_str());
}
return map.at(std::move(node_key));
}
/// @brief Get a basic_node object with a basic_node key object.
/// @tparam KeyType A key type which is a kind of the basic_node template class.
/// @param key A key to the target basic_node object in a sequence/mapping node.
/// @return Reference to the basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/at/
template <typename KeyType, detail::enable_if_t<detail::is_basic_node<KeyType>::value, int> = 0>
basic_node& at(KeyType&& key) {
basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("at() is unavailable for a scalar node.", get_type());
}
if (act_node.is_sequence_impl()) {
if FK_YAML_UNLIKELY (!key.is_integer()) {
throw fkyaml::type_error("An argument of at() for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
sequence_type& seq = *act_node.m_value.p_seq;
int index = std::forward<KeyType>(key).template get_value<int>();
int size = static_cast<int>(seq.size());
if FK_YAML_UNLIKELY (index >= size) {
throw fkyaml::out_of_range(index);
}
return seq[index];
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
mapping_type& map = *act_node.m_value.p_map;
bool is_found = map.find(key) != map.end();
if FK_YAML_UNLIKELY (!is_found) {
throw fkyaml::out_of_range(serialize(key).c_str());
}
return map[std::forward<KeyType>(key)];
}
/// @brief Get a basic_node object with a basic_node key object.
/// @tparam KeyType A key type which is a kind of the basic_node template class.
/// @param key A key to the target basic_node object in a sequence/mapping node.
/// @return Constant reference to the basic_node object associated with the given key.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/at/
template <typename KeyType, detail::enable_if_t<detail::is_basic_node<KeyType>::value, int> = 0>
const basic_node& at(KeyType&& key) const {
const basic_node& act_node = resolve_reference();
if FK_YAML_UNLIKELY (act_node.is_scalar_impl()) {
throw fkyaml::type_error("at() is unavailable for a scalar node.", get_type());
}
if (act_node.is_sequence_impl()) {
if FK_YAML_UNLIKELY (!key.is_integer()) {
throw fkyaml::type_error("An argument of at() for sequence nodes must be an integer.", get_type());
}
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
const sequence_type& seq = *act_node.m_value.p_seq;
int index = std::forward<KeyType>(key).template get_value<int>();
int size = static_cast<int>(seq.size());
if FK_YAML_UNLIKELY (index >= size) {
throw fkyaml::out_of_range(index);
}
return seq[index];
}
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
const mapping_type& map = *act_node.m_value.p_map;
bool is_found = map.find(key) != map.end();
if FK_YAML_UNLIKELY (!is_found) {
throw fkyaml::out_of_range(serialize(key).c_str());
}
return map.at(std::forward<KeyType>(key));
}
/// @brief Get the YAML version for this basic_node object.
/// @return The YAML version if already set, `yaml_version_type::VERSION_1_2` otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_yaml_version_type/
yaml_version_type get_yaml_version_type() const noexcept {
return mp_meta->is_version_specified ? mp_meta->version : yaml_version_type::VERSION_1_2;
}
/// @brief Set the YAML version for this basic_node object.
/// @param[in] version The target YAML version.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/set_yaml_version_type/
void set_yaml_version_type(const yaml_version_type version) noexcept {
mp_meta->version = version;
mp_meta->is_version_specified = true;
}
/// @brief Get the YAML version for this basic_node object.
/// @deprecated Use get_yaml_version_type() function. (since 0.3.12)
/// @return The YAML version if already set, `yaml_version_t::VER_1_2` otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_yaml_version/
FK_YAML_DEPRECATED("Since 0.3.12; Use get_yaml_version_type()")
yaml_version_t get_yaml_version() const noexcept {
yaml_version_type tmp_type = get_yaml_version_type();
return detail::convert_from_yaml_version_type(tmp_type);
}
/// @brief Set the YAML version for this basic_node object.
/// @deprecated Use set_yaml_version_type(yaml_version_type) function. (since 0.3.12)
/// @param[in] version The target YAML version.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/set_yaml_version/
FK_YAML_DEPRECATED("Since 0.3.12; Use set_yaml_version_type(const yaml_version_type)")
void set_yaml_version(const yaml_version_t version) noexcept {
set_yaml_version_type(detail::convert_to_yaml_version_type(version));
}
/// @brief Check whether this basic_node object has already had any anchor name.
/// @return true if ths basic_node has an anchor name, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/has_anchor_name/
bool has_anchor_name() const noexcept {
return (m_attrs & detail::node_attr_mask::anchoring) && !m_prop.anchor.empty();
}
/// @brief Get the anchor name associated with this basic_node object.
/// @note Some anchor name must be set before calling this method. Call has_anchor_name() to see if this basic_node
/// object has any anchor name.
/// @return The anchor name associated with the node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_anchor_name/
const std::string& get_anchor_name() const {
if FK_YAML_UNLIKELY (!has_anchor_name()) {
throw fkyaml::exception("No anchor name has been set.");
}
return m_prop.anchor;
}
/// @brief Add an anchor name to this basic_node object.
/// @note If this basic_node object has already had any anchor name, the new anchor name will overwrite the old one.
/// @param[in] anchor_name An anchor name. This should not be empty.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/add_anchor_name/
void add_anchor_name(const std::string& anchor_name) {
if (is_anchor()) {
m_attrs &= ~detail::node_attr_mask::anchoring;
auto itr = mp_meta->anchor_table.equal_range(m_prop.anchor).first;
std::advance(itr, detail::node_attr_bits::get_anchor_offset(m_attrs));
mp_meta.reset();
itr->second.swap(*this);
mp_meta->anchor_table.erase(itr);
}
auto p_meta = mp_meta;
basic_node node;
node.swap(*this);
p_meta->anchor_table.emplace(anchor_name, std::move(node));
m_attrs &= ~detail::node_attr_mask::anchoring;
m_attrs |= detail::node_attr_bits::anchor_bit;
mp_meta = p_meta;
const auto offset = static_cast<uint32_t>(mp_meta->anchor_table.count(anchor_name) - 1);
detail::node_attr_bits::set_anchor_offset(offset, m_attrs);
m_prop.anchor = anchor_name;
}
/// @brief Add an anchor name to this basic_node object.
/// @note If this basic_node object has already had any anchor name, the new anchor name will overwrite the old one.
/// @param[in] anchor_name An anchor name. This should not be empty.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/add_anchor_name/
void add_anchor_name(std::string&& anchor_name) {
if (is_anchor()) {
m_attrs &= ~detail::node_attr_mask::anchoring;
auto itr = mp_meta->anchor_table.equal_range(m_prop.anchor).first;
std::advance(itr, detail::node_attr_bits::get_anchor_offset(m_attrs));
mp_meta.reset();
itr->second.swap(*this);
mp_meta->anchor_table.erase(itr);
}
auto p_meta = mp_meta;
basic_node node;
node.swap(*this);
p_meta->anchor_table.emplace(anchor_name, std::move(node));
m_attrs &= ~detail::node_attr_mask::anchoring;
m_attrs |= detail::node_attr_bits::anchor_bit;
mp_meta = p_meta;
auto offset = static_cast<uint32_t>(mp_meta->anchor_table.count(anchor_name) - 1);
detail::node_attr_bits::set_anchor_offset(offset, m_attrs);
m_prop.anchor = std::move(anchor_name);
}
/// @brief Check whether this basic_node object has already had any tag name.
/// @return true if ths basic_node has a tag name, false otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/has_tag_name/
bool has_tag_name() const noexcept {
return !m_prop.tag.empty();
}
/// @brief Get the tag name associated with this basic_node object.
/// @note Some tag name must be set before calling this method. Call has_tag_name() to see if this basic_node
/// object has any tag name.
/// @return The tag name associated with the node. It may be empty.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_tag_name/
const std::string& get_tag_name() const {
if FK_YAML_UNLIKELY (!has_tag_name()) {
throw fkyaml::exception("No tag name has been set.");
}
return m_prop.tag;
}
/// @brief Add a tag name to this basic_node object.
/// @note If this basic_node object has already had any tag name, the new tag name will overwrite the old one.
/// @param[in] tag_name A tag name to get associated with this basic_node object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/add_tag_name/
void add_tag_name(const std::string& tag_name) {
m_prop.tag = tag_name;
}
/// @brief Add a tag name to this basic_node object.
/// @note If this basic_node object has already had any tag name, the new tag name will overwrite the old one.
/// @param[in] tag_name A tag name to get associated with this basic_node object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/add_tag_name/
void add_tag_name(std::string&& tag_name) {
m_prop.tag = std::move(tag_name);
}
/// @brief Get the node value object converted into a given type.
/// @note This function requires T objects to be default constructible. Also, T cannot be either a reference,
/// pointer or C-style array type.
/// @tparam T A compatible value type which may be cv-qualified.
/// @tparam ValueType A compatible value type (T without cv-qualifiers by default).
/// @return A value converted from this basic_node object.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_value/
template <
typename T, typename ValueType = detail::remove_cv_t<T>,
detail::enable_if_t<
detail::conjunction<std::is_default_constructible<ValueType>, detail::negation<std::is_pointer<T>>>::value,
int> = 0>
T get_value() const noexcept(
noexcept(std::declval<const basic_node&>().template get_value_impl<ValueType>(std::declval<ValueType&>()))) {
// emit a compile error if T is either a reference, pointer or C-style array type.
static_assert(
!std::is_reference<T>::value,
"get_value() cannot be called with reference types. "
"You might want to call one of as_seq(), as_map(), as_bool(), as_int(), as_float() or as_str().");
static_assert(
!std::is_array<T>::value,
"get_value() cannot be called with C-style array types. You might want to call get_value_inplace().");
auto ret = ValueType();
resolve_reference().get_value_impl(ret);
return ret;
}
/// @brief Get the node value object converted into a given type. The conversion result is filled into `value_ref`.
/// @tparam T A compatible value type.
/// @param value_ref A storage into which the conversion result is filled.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_value_inplace/
template <typename T>
void get_value_inplace(T& value_ref) const
noexcept(noexcept(std::declval<const basic_node&>().template get_value_impl<T>(std::declval<T&>()))) {
resolve_reference().get_value_impl(value_ref);
}
/// @brief Get the node value object converted to a given type. If the conversion fails, this function returns a
/// given default value instead.
/// @note This function requires T to be default constructible. Also, T cannot be either a reference, pointer or
/// C-style array type.
/// @tparam T A compatible value type which may be cv-qualified.
/// @tparam U A default value type from which T must be constructible.
/// @param default_value The default value returned if conversion fails.
/// @return A value converted from this basic_node object if conversion succeeded, the given default value
/// otherwise.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_value_or/
template <
typename T, typename U,
detail::enable_if_t<
detail::conjunction<
std::is_constructible<T, U>, std::is_default_constructible<T>,
detail::negation<std::is_pointer<T>>>::value,
int> = 0>
T get_value_or(U&& default_value) const noexcept {
static_assert(
!std::is_reference<T>::value,
"get_value_or() cannot be called with reference types. "
"You might want to call one of as_seq(), as_map(), as_bool(), as_int(), as_float() or as_str().");
static_assert(
!std::is_array<T>::value,
"get_value_or() cannot be called with C-style array types. You might want to call get_value_inplace().");
// TODO:
// Ideally, there should be no exception thrown in this kind of function. However, achieving that would require
// a lot of refactoring and/or some API changes, especially `from_node` interface definition. So, try-catch is
// used instead for now.
try {
return get_value<T>();
}
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.
return std::forward<U>(default_value);
}
}
/// @brief Explicit reference access to the internally stored YAML node value.
/// @tparam ReferenceType Reference type to the target YAML node value.
/// @return Reference to the internally stored YAML node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_value_ref/
template <typename ReferenceType, detail::enable_if_t<std::is_reference<ReferenceType>::value, int> = 0>
FK_YAML_DEPRECATED("Since 0.4.3; Use one of as_seq(), as_map(), as_bool(), as_int(), as_float() or as_str()")
ReferenceType get_value_ref() {
return get_value_ref_impl(static_cast<detail::add_pointer_t<ReferenceType>>(nullptr));
}
/// @brief Explicit reference access to the internally stored YAML node value.
/// @tparam ReferenceType Constant reference type to the target YAML node value.
/// @return Constant reference to the internally stored YAML node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/get_value_ref/
template <
typename ReferenceType,
detail::enable_if_t<
detail::conjunction<
std::is_reference<ReferenceType>, std::is_const<detail::remove_reference_t<ReferenceType>>>::value,
int> = 0>
FK_YAML_DEPRECATED("Since 0.4.3; Use one of as_seq(), as_map(), as_bool(), as_int(), as_float() or as_str()")
ReferenceType get_value_ref() const {
return get_value_ref_impl(static_cast<detail::add_pointer_t<ReferenceType>>(nullptr));
}
/// @brief Returns reference to the sequence node value.
/// @throw fkyaml::type_error The node value is not a sequence.
/// @return Reference to the sequence node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_seq/
sequence_type& as_seq() {
basic_node& act_node = resolve_reference(); // NOLINT(misc-const-correctness)
if FK_YAML_LIKELY (act_node.is_sequence_impl()) {
return *act_node.m_value.p_seq;
}
throw fkyaml::type_error("The node value is not a sequence.", get_type());
}
/// @brief Returns constant reference to the sequence node value.
/// @throw fkyaml::type_error The node value is not a sequence.
/// @return Constant reference to the sequence node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_seq/
const sequence_type& as_seq() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_sequence_impl()) {
return *act_node.m_value.p_seq;
}
throw fkyaml::type_error("The node value is not a sequence.", get_type());
}
/// @brief Returns reference to the mapping node value.
/// @throw fkyaml::type_error The node value is not a mapping.
/// @return Reference to the mapping node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_map/
mapping_type& as_map() {
basic_node& act_node = resolve_reference(); // NOLINT(misc-const-correctness)
if FK_YAML_LIKELY (act_node.is_mapping_impl()) {
return *act_node.m_value.p_map;
}
throw fkyaml::type_error("The node value is not a mapping.", get_type());
}
/// @brief Returns constant reference to the mapping node value.
/// @throw fkyaml::type_error The node value is not a mapping.
/// @return Constant reference to the mapping node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_map/
const mapping_type& as_map() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_mapping_impl()) {
return *act_node.m_value.p_map;
}
throw fkyaml::type_error("The node value is not a mapping.", get_type());
}
/// @brief Returns reference to the boolean node value.
/// @throw fkyaml::type_error The node value is not a boolean.
/// @return Reference to the boolean node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_bool/
boolean_type& as_bool() {
basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_boolean_impl()) {
return act_node.m_value.boolean;
}
throw fkyaml::type_error("The node value is not a boolean.", get_type());
}
/// @brief Returns reference to the boolean node value.
/// @throw fkyaml::type_error The node value is not a boolean.
/// @return Constant reference to the boolean node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_bool/
const boolean_type& as_bool() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_boolean_impl()) {
return act_node.m_value.boolean;
}
throw fkyaml::type_error("The node value is not a boolean.", 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.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_int/
integer_type& as_int() {
basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_integer_impl()) {
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.
/// @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()) {
return act_node.m_value.integer;
}
throw fkyaml::type_error("The node value is not an integer.", get_type());
}
/// @brief Returns reference to the float node value.
/// @throw fkyaml::type_error The node value is not a float.
/// @return Reference to the float node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_float/
float_number_type& as_float() {
basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_float_number_impl()) {
return act_node.m_value.float_val;
}
throw fkyaml::type_error("The node value is not a float.", get_type());
}
/// @brief Returns reference to the float node value.
/// @throw fkyaml::type_error The node value is not a float.
/// @return Constant reference to the float node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_float/
const float_number_type& as_float() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_float_number_impl()) {
return act_node.m_value.float_val;
}
throw fkyaml::type_error("The node value is not a float.", get_type());
}
/// @brief Returns reference to the string node value.
/// @throw fkyaml::type_error The node value is not a string.
/// @return Reference to the string node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_str/
string_type& as_str() {
basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_string_impl()) {
return *act_node.m_value.p_str;
}
throw fkyaml::type_error("The node value is not a string.", get_type());
}
/// @brief Returns reference to the string node value.
/// @throw fkyaml::type_error The node value is not a string.
/// @return Constant reference to the string node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_str/
const string_type& as_str() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_string_impl()) {
return *act_node.m_value.p_str;
}
throw fkyaml::type_error("The node value is not a string.", get_type());
}
/// @brief Swaps the internally stored data with the specified basic_node object.
/// @param[in] rhs A basic_node object to be swapped with.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/swap/
void swap(basic_node& rhs) noexcept {
using std::swap;
swap(m_attrs, rhs.m_attrs);
swap(mp_meta, rhs.mp_meta);
node_value tmp {};
std::memcpy(&tmp, &m_value, sizeof(node_value));
std::memcpy(&m_value, &rhs.m_value, sizeof(node_value));
std::memcpy(&rhs.m_value, &tmp, sizeof(node_value));
swap(m_prop.tag, rhs.m_prop.tag);
swap(m_prop.anchor, rhs.m_prop.anchor);
}
/// @brief Returns an iterator to the first element of a container node (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return An iterator to the first element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/begin/
iterator begin() {
basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return {act_node.m_value.p_seq->begin()};
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return {act_node.m_value.p_map->begin()};
default:
throw fkyaml::type_error("The target node is neither of sequence nor mapping types.", get_type());
}
}
/// @brief Returns a const iterator to the first element of a container node (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the first element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/begin/
const_iterator begin() const {
const basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return {act_node.m_value.p_seq->begin()};
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return {act_node.m_value.p_map->begin()};
default:
throw fkyaml::type_error("The target node is neither of sequence nor mapping types.", get_type());
}
}
/// @brief Returns a const iterator to the first element of a container node (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the first element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/begin/
const_iterator cbegin() const {
return begin();
}
/// @brief Returns an iterator to the past-the-last element of a container node (sequence or mapping).
/// @throw `type_error` if the basic_node value is not of container types.
/// @return An iterator to the past-the-last element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/end/
iterator end() {
basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return {act_node.m_value.p_seq->end()};
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return {act_node.m_value.p_map->end()};
default:
throw fkyaml::type_error("The target node is neither of sequence nor mapping types.", get_type());
}
}
/// @brief Returns a const iterator to the past-the-last element of a container node (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the past-the-last element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/end/
const_iterator end() const {
const basic_node& act_node = resolve_reference();
switch (act_node.m_attrs & detail::node_attr_mask::value) {
case detail::node_attr_bits::seq_bit:
FK_YAML_ASSERT(act_node.m_value.p_seq != nullptr);
return {act_node.m_value.p_seq->end()};
case detail::node_attr_bits::map_bit:
FK_YAML_ASSERT(act_node.m_value.p_map != nullptr);
return {act_node.m_value.p_map->end()};
default:
throw fkyaml::type_error("The target node is neither of sequence nor mapping types.", get_type());
}
}
/// @brief Returns a const iterator to the past-the-last element of a container node (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the past-the-last element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/end/
const_iterator cend() const {
return end();
}
/// @brief Returns an iterator to the reverse-beginning (i.e., last) element of a container node (sequence or
/// mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return An iterator to the reverse-beginning element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rbegin/
reverse_iterator rbegin() {
return {end()};
}
/// @brief Returns a const iterator to the reverse-beginning (i.e., last) element of a container node (sequence or
/// mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the reverse-beginning element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rbegin/
const_reverse_iterator rbegin() const {
return {end()};
}
/// @brief Returns a const iterator to the reverse-beginning (i.e., last) element of a container node (sequence or
/// mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the reverse-beginning element of a container node.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rbegin/
const_reverse_iterator crbegin() const {
return rbegin();
}
/// @brief Returns an iterator to the reverse-end (i.e., one before the first) element of a container node (sequence
/// or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return An iterator to the reverse-end element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rend/
reverse_iterator rend() {
return {begin()};
}
/// @brief Returns a const iterator to the reverse-end (i.e., one before the first) element of a container node
/// (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the reverse-end element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rend/
const_reverse_iterator rend() const {
return {begin()};
}
/// @brief Returns a const iterator to the reverse-end (i.e., one before the first) element of a container node
/// (sequence or mapping).
/// @throw `type_error` if this basic_node is neither a sequence nor mapping node.
/// @return A const iterator to the reverse-end element.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/rend/
const_reverse_iterator crend() const {
return rend();
}
/// @brief Returns a range of mapping entries.
/// @throw `type_error` if this basic_node is not a mapping.
/// @return A range of mapping entries.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/map_items/
map_range map_items() {
if FK_YAML_UNLIKELY (!is_mapping()) {
throw type_error("map_items() cannot be called on a non-mapping node.", get_type());
}
return {*this};
}
/// @brief Returns a const range of mapping entries.
/// @throw `type_error` if this basic_node is not a mapping.
/// @return A const range of mapping entries.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/map_items/
const_map_range map_items() const {
if FK_YAML_UNLIKELY (!is_mapping()) {
throw type_error("map_items() cannot be called on a non-mapping node.", get_type());
}
return {*this};
}
private:
/// @brief Resolves anchor/alias reference and returns reference to an actual value node.
/// @return Reference to an actual value node.
basic_node& resolve_reference() {
if FK_YAML_UNLIKELY (has_anchor_name()) {
auto itr = mp_meta->anchor_table.equal_range(m_prop.anchor).first;
std::advance(itr, detail::node_attr_bits::get_anchor_offset(m_attrs));
return itr->second;
}
return *this;
}
/// @brief Resolves anchor/alias reference and returns const reference to an actual value node.
/// @return Const reference to an actual value node.
const basic_node& resolve_reference() const {
if FK_YAML_UNLIKELY (has_anchor_name()) {
auto itr = mp_meta->anchor_table.equal_range(m_prop.anchor).first;
std::advance(itr, detail::node_attr_bits::get_anchor_offset(m_attrs));
return itr->second;
}
return *this;
}
bool is_sequence_impl() const noexcept {
return m_attrs & detail::node_attr_bits::seq_bit;
}
bool is_mapping_impl() const noexcept {
return m_attrs & detail::node_attr_bits::map_bit;
}
bool is_null_impl() const noexcept {
return m_attrs & detail::node_attr_bits::null_bit;
}
bool is_boolean_impl() const noexcept {
return m_attrs & detail::node_attr_bits::bool_bit;
}
bool is_integer_impl() const noexcept {
return m_attrs & detail::node_attr_bits::int_bit;
}
bool is_float_number_impl() const noexcept {
return m_attrs & detail::node_attr_bits::float_bit;
}
bool is_string_impl() const noexcept {
return m_attrs & detail::node_attr_bits::string_bit;
}
bool is_scalar_impl() const noexcept {
return m_attrs & detail::node_attr_bits::scalar_bits;
}
template <
typename ValueType, detail::enable_if_t<detail::negation<detail::is_basic_node<ValueType>>::value, int> = 0>
void get_value_impl(ValueType& v) const
noexcept(noexcept(ConverterType<ValueType, void>::from_node(std::declval<const basic_node&>(), v))) {
ConverterType<ValueType, void>::from_node(*this, v);
}
template <typename ValueType, detail::enable_if_t<detail::is_basic_node<ValueType>::value, int> = 0>
void get_value_impl(ValueType& v) const {
v = *this;
}
/// @brief Returns reference to the sequence node value.
/// @throw fkyaml::exception The node value is not a sequence.
/// @return Reference to the sequence node value.
sequence_type& get_value_ref_impl(sequence_type* /*unused*/) {
return as_seq();
}
/// @brief Returns constant reference to the sequence node value.
/// @throw fkyaml::exception The node value is not a sequence.
/// @return Constant reference to the sequence node value.
const sequence_type& get_value_ref_impl(const sequence_type* /*unused*/) const {
return as_seq();
}
/// @brief Returns reference to the mapping node value.
/// @throw fkyaml::exception The node value is not a mapping.
/// @return Reference to the mapping node value.
mapping_type& get_value_ref_impl(mapping_type* /*unused*/) {
return as_map();
}
/// @brief Returns constant reference to the mapping node value.
/// @throw fkyaml::exception The node value is not a mapping.
/// @return Constant reference to the mapping node value.
const mapping_type& get_value_ref_impl(const mapping_type* /*unused*/) const {
return as_map();
}
/// @brief Returns reference to the boolean node value.
/// @throw fkyaml::exception The node value is not a boolean.
/// @return Reference to the boolean node value.
boolean_type& get_value_ref_impl(boolean_type* /*unused*/) {
return as_bool();
}
/// @brief Returns reference to the boolean node value.
/// @throw fkyaml::exception The node value is not a boolean.
/// @return Constant reference to the boolean node value.
const boolean_type& get_value_ref_impl(const boolean_type* /*unused*/) const {
return as_bool();
}
/// @brief Returns reference to the integer node value.
/// @throw fkyaml::exception The node value is not an integer.
/// @return Reference to the integer node value.
integer_type& get_value_ref_impl(integer_type* /*unused*/) {
return as_int();
}
/// @brief Returns reference to the integer node value.
/// @throw fkyaml::exception The node value is not an integer.
/// @return Constant reference to the integer node value.
const integer_type& get_value_ref_impl(const integer_type* /*unused*/) const {
return as_int();
}
/// @brief Returns reference to the floating point number node value.
/// @throw fkyaml::exception The node value is not a floating point number.
/// @return Reference to the floating point number node value.
float_number_type& get_value_ref_impl(float_number_type* /*unused*/) {
return as_float();
}
/// @brief Returns reference to the floating point number node value.
/// @throw fkyaml::exception The node value is not a floating point number.
/// @return Constant reference to the floating point number node value.
const float_number_type& get_value_ref_impl(const float_number_type* /*unused*/) const {
return as_float();
}
/// @brief Returns reference to the string node value.
/// @throw fkyaml::exception The node value is not a string.
/// @return Reference to the string node value.
string_type& get_value_ref_impl(string_type* /*unused*/) {
return as_str();
}
/// @brief Returns reference to the string node value.
/// @throw fkyaml::exception The node value is not a string.
/// @return Constant reference to the string node value.
const string_type& get_value_ref_impl(const string_type* /*unused*/) const {
return as_str();
}
/// The current node attributes.
detail::node_attr_t m_attrs {detail::node_attr_bits::default_bits};
/// The shared set of YAML directives applied to this node.
mutable std::shared_ptr<detail::document_metainfo<basic_node>> mp_meta {
// NOLINTNEXTLINE(bugprone-unhandled-exception-at-new)
std::shared_ptr<detail::document_metainfo<basic_node>>(new detail::document_metainfo<basic_node>())};
/// The current node value.
node_value m_value {};
/// The property set of this node.
detail::node_property m_prop {};
};
/// @brief Swap function for basic_node objects.
/// @param[in] lhs A left-side-hand basic_node object to be swapped with.
/// @param[in] rhs A right-side-hand basic_node object to be swapped with.
/// @sa https://fktn-k.github.io/fkYAML/api/swap/
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename = void> class ConverterType>
inline void swap(
basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>& lhs,
basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>&
rhs) noexcept(noexcept(lhs.swap(rhs))) {
lhs.swap(rhs);
}
/// @brief Insertion operator for basic_node template class. A wrapper for the serialization feature.
/// @param[in] os An output stream object.
/// @param[in] n A basic_node object.
/// @return Reference to the output stream object `os`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/insertion_operator/
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename = void> class ConverterType>
inline std::ostream& operator<<(
std::ostream& os,
const basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>&
n) {
os << basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>::
serialize(n);
return os;
}
/// @brief Extraction operator for basic_node template class. A wrapper for the deserialization feature with input
/// streams.
/// @param[in] is An input stream object.
/// @param[in] n A basic_node object.
/// @return Reference to the input stream object `is`.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/extraction_operator/
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename = void> class ConverterType>
inline std::istream& operator>>(
std::istream& is,
basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>& n) {
n = basic_node<SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>::
deserialize(is);
return is;
}
/// @brief namespace for user-defined literals for the fkYAML library.
inline namespace literals {
/// @brief namespace for user-defined literals for YAML node objects.
inline namespace yaml_literals {
// Whitespace before the literal operator identifier is deprecated in C++23 or better but required in C++11.
// Ignore the warning as a workaround. https://github.com/fktn-k/fkYAML/pull/417
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
#endif
#if defined(__GNUC__) && (__GNUC__ > 6)
#define FK_YAML_QUOTE_OPERATOR operator""_yaml
#else
#define FK_YAML_QUOTE_OPERATOR operator"" _yaml
#endif
/// @brief The user-defined string literal which deserializes a `char` array into a `node` object.
/// @param s An input `char` array.
/// @param n The size of `s`.
/// @return The resulting `node` object deserialized from `s`.
/// @sa https://fktn-k.github.io/fkYAML/api/operator_literal_yaml/
inline fkyaml::node FK_YAML_QUOTE_OPERATOR(const char* s, std::size_t n) {
return fkyaml::node::deserialize(s, s + n);
}
/// @brief The user-defined string literal which deserializes a `char16_t` array into a `node` object.
/// @param s An input `char16_t` array.
/// @param n The size of `s`.
/// @return The resulting `node` object deserialized from `s`.
/// @sa https://fktn-k.github.io/fkYAML/api/operator_literal_yaml/
inline fkyaml::node FK_YAML_QUOTE_OPERATOR(const char16_t* s, std::size_t n) {
return fkyaml::node::deserialize(s, s + n);
}
/// @brief The user-defined string literal which deserializes a `char32_t` array into a `node` object.
/// @param s An input `char32_t` array.
/// @param n The size of `s`.
/// @return The resulting `node` object deserialized from `s`.
/// @sa https://fktn-k.github.io/fkYAML/api/operator_literal_yaml/
inline fkyaml::node FK_YAML_QUOTE_OPERATOR(const char32_t* s, std::size_t n) {
return fkyaml::node::deserialize(s, s + n);
}
#if FK_YAML_HAS_CHAR8_T
/// @brief The user-defined string literal which deserializes a `char8_t` array into a `node` object.
/// @param s An input `char8_t` array.
/// @param n The size of `s`.
/// @return The resulting `node` object deserialized from `s`.
inline fkyaml::node FK_YAML_QUOTE_OPERATOR(const char8_t* s, std::size_t n) {
return fkyaml::node::deserialize(s, s + n);
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif
} // namespace yaml_literals
} // namespace literals
FK_YAML_NAMESPACE_END
namespace std {
template <
template <typename, typename...> class SequenceType, template <typename, typename, typename...> class MappingType,
typename BooleanType, typename IntegerType, typename FloatNumberType, typename StringType,
template <typename, typename = void> class ConverterType>
// NOLINTNEXTLINE(cert-dcl58-cpp)
struct hash<fkyaml::basic_node<
SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>> {
using node_t = fkyaml::basic_node<
SequenceType, MappingType, BooleanType, IntegerType, FloatNumberType, StringType, ConverterType>;
std::size_t operator()(const node_t& n) const {
using boolean_type = typename node_t::boolean_type;
using integer_type = typename node_t::integer_type;
using float_number_type = typename node_t::float_number_type;
using string_type = typename node_t::string_type;
const auto type = n.get_type();
std::size_t seed = 0;
hash_combine(seed, std::hash<uint8_t>()(static_cast<uint8_t>(type)));
switch (type) {
case fkyaml::node_type::SEQUENCE:
hash_combine(seed, n.size());
for (const auto& elem : n) {
hash_combine(seed, operator()(elem));
}
return seed;
case fkyaml::node_type::MAPPING:
hash_combine(seed, n.size());
for (auto itr = n.begin(), end_itr = n.end(); itr != end_itr; ++itr) {
hash_combine(seed, operator()(itr.key()));
hash_combine(seed, operator()(itr.value()));
}
return seed;
case fkyaml::node_type::NULL_OBJECT:
hash_combine(seed, 0);
return seed;
case fkyaml::node_type::BOOLEAN:
hash_combine(seed, std::hash<boolean_type>()(n.template get_value<boolean_type>()));
return seed;
case fkyaml::node_type::INTEGER:
hash_combine(seed, std::hash<integer_type>()(n.template get_value<integer_type>()));
return seed;
case fkyaml::node_type::FLOAT:
hash_combine(seed, std::hash<float_number_type>()(n.template get_value<float_number_type>()));
return seed;
case fkyaml::node_type::STRING:
hash_combine(seed, std::hash<string_type>()(n.template get_value<string_type>()));
return seed;
default: // LCOV_EXCL_LINE
fkyaml::detail::unreachable(); // LCOV_EXCL_LINE
}
}
private:
// taken from boost::hash_combine
FK_YAML_NO_SANITIZE("unsigned-shift-base", "unsigned-integer-overflow")
static void hash_combine(std::size_t& seed, std::size_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6u) + (seed >> 2u);
}
};
} // namespace std
#endif /* FK_YAML_NODE_HPP */