diff --git a/include/toml++/impl/array.hpp b/include/toml++/impl/array.hpp index ad793794..db339589 100644 --- a/include/toml++/impl/array.hpp +++ b/include/toml++/impl/array.hpp @@ -7,6 +7,8 @@ #include "std_utility.hpp" #include "std_vector.hpp" #include "std_initializer_list.hpp" +#include "preprocessor.hpp" +#include "trivia_piece.hpp" #include "value.hpp" #include "make_node.hpp" #include "header_start.hpp" @@ -290,6 +292,7 @@ TOML_NAMESPACE_START using vector_iterator = typename vector_type::iterator; using const_vector_iterator = typename vector_type::const_iterator; vector_type elems_; + optional> inner_trailing_trivia_; TOML_NODISCARD_CTOR TOML_EXPORTED_MEMBER_FUNCTION @@ -404,6 +407,20 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION array& operator=(array&& rhs) noexcept; + /// \brief Gets the inner trailing trivia. + TOML_CONST_INLINE_GETTER + const optional> inner_trailing_trivia() const noexcept + { + return inner_trailing_trivia_; + } + + /// \brief Sets the inner trailing trivia. + TOML_EXPORTED_MEMBER_FUNCTION + void set_inner_trailing_trivia(optional> trivia) noexcept + { + inner_trailing_trivia_ = trivia; + } + /// \name Type checks /// @{ diff --git a/include/toml++/impl/array.inl b/include/toml++/impl/array.inl index a3c62434..8869634c 100644 --- a/include/toml++/impl/array.inl +++ b/include/toml++/impl/array.inl @@ -65,7 +65,8 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE array::array(const array& other) // - : node(other) + : node(other), + inner_trailing_trivia_(other.inner_trailing_trivia_) { elems_.reserve(other.elems_.size()); for (const auto& elem : other) @@ -79,7 +80,8 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE array::array(array && other) noexcept // : node(std::move(other)), - elems_(std::move(other.elems_)) + elems_(std::move(other.elems_)), + inner_trailing_trivia_(other.inner_trailing_trivia_) { #if TOML_LIFETIME_HOOKS TOML_ARRAY_CREATED; @@ -96,6 +98,7 @@ TOML_NAMESPACE_START elems_.reserve(rhs.elems_.size()); for (const auto& elem : rhs) elems_.emplace_back(impl::make_node(elem)); + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } @@ -107,6 +110,7 @@ TOML_NAMESPACE_START { node::operator=(std::move(rhs)); elems_ = std::move(rhs.elems_); + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } diff --git a/include/toml++/impl/date_time.hpp b/include/toml++/impl/date_time.hpp index 8957ad33..55b6e29b 100644 --- a/include/toml++/impl/date_time.hpp +++ b/include/toml++/impl/date_time.hpp @@ -7,6 +7,7 @@ #include "forward_declarations.hpp" #include "print_to_stream.hpp" #include "header_start.hpp" +#include "std_optional.hpp" TOML_NAMESPACE_START { diff --git a/include/toml++/impl/formatter.hpp b/include/toml++/impl/formatter.hpp index 0c97833f..1738ad45 100644 --- a/include/toml++/impl/formatter.hpp +++ b/include/toml++/impl/formatter.hpp @@ -130,6 +130,12 @@ TOML_IMPL_NAMESPACE_START return !!(config_.flags & format_flags::terse_key_value_pairs); } + TOML_PURE_INLINE_GETTER + bool preserve_source_trivia() const noexcept + { + return !!(config_.flags & format_flags::preserve_source_trivia); + } + TOML_EXPORTED_MEMBER_FUNCTION void attach(std::ostream& stream) noexcept; diff --git a/include/toml++/impl/forward_declarations.hpp b/include/toml++/impl/forward_declarations.hpp index 386a9e06..ceb04863 100644 --- a/include/toml++/impl/forward_declarations.hpp +++ b/include/toml++/impl/forward_declarations.hpp @@ -343,6 +343,9 @@ TOML_NAMESPACE_START // abi namespace /// \brief Avoids the use of whitespace around key-value pairs. terse_key_value_pairs = (1ull << 12), + + /// \brief Preserves trivia of nodes that have it (i.e. were passed with collect_trivia enabled). + preserve_source_trivia = (1ull << 13) }; TOML_MAKE_FLAGS(format_flags); diff --git a/include/toml++/impl/key.hpp b/include/toml++/impl/key.hpp index 575017a8..6dde61c5 100644 --- a/include/toml++/impl/key.hpp +++ b/include/toml++/impl/key.hpp @@ -4,6 +4,8 @@ // SPDX-License-Identifier: MIT #pragma once +#include "std_vector.hpp" +#include "trivia_piece.hpp" #include "source_region.hpp" #include "std_utility.hpp" #include "print_to_stream.hpp" @@ -33,6 +35,8 @@ TOML_NAMESPACE_START private: std::string key_; source_region source_; + std::optional> leading_trivia_; + std::optional> trailing_trivia_; public: /// \brief Default constructor. @@ -41,44 +45,74 @@ TOML_NAMESPACE_START /// \brief Constructs a key from a string view and source region. TOML_NODISCARD_CTOR - explicit key(std::string_view k, source_region&& src = {}) // + explicit key(std::string_view k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a string view and source region. TOML_NODISCARD_CTOR - explicit key(std::string_view k, const source_region& src) // + explicit key(std::string_view k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a string and source region. TOML_NODISCARD_CTOR - explicit key(std::string&& k, source_region&& src = {}) noexcept // + explicit key(std::string&& k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) noexcept // : key_{ std::move(k) }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a string and source region. TOML_NODISCARD_CTOR - explicit key(std::string&& k, const source_region& src) noexcept // + explicit key(std::string&& k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) noexcept // : key_{ std::move(k) }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a c-string and source region. TOML_NODISCARD_CTOR - explicit key(const char* k, source_region&& src = {}) // + explicit key(const char* k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a c-string view and source region. TOML_NODISCARD_CTOR - explicit key(const char* k, const source_region& src) // + explicit key(const char* k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} #if TOML_ENABLE_WINDOWS_COMPAT @@ -87,18 +121,28 @@ TOML_NAMESPACE_START /// /// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. TOML_NODISCARD_CTOR - explicit key(std::wstring_view k, source_region&& src = {}) // + explicit key(std::wstring_view k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ impl::narrow(k) }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} /// \brief Constructs a key from a wide string and source region. /// /// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. TOML_NODISCARD_CTOR - explicit key(std::wstring_view k, const source_region& src) // + explicit key(std::wstring_view k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ impl::narrow(k) }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} #endif @@ -155,6 +199,23 @@ TOML_NAMESPACE_START /// @} + /// \name Metadata + /// @{ + + /// \brief Returns the key's leading trivia. + optional> leading_trivia() const noexcept + { + return leading_trivia_; + } + + /// \brief Returns the key's trailing trivia. + optional> trailing_trivia() const noexcept + { + return trailing_trivia_; + } + + /// @} + /// \name Equality and Comparison /// \attention These operations only compare the underlying strings; source regions are ignored for the purposes of all comparison! /// @{ @@ -332,4 +393,10 @@ TOML_NAMESPACE_START } TOML_NAMESPACE_END; +template<> struct std::hash { + std::size_t operator()(toml::key const& k) const noexcept { + return std::hash{}(k.str()); + } +}; + #include "header_end.hpp" diff --git a/include/toml++/impl/node.hpp b/include/toml++/impl/node.hpp index e4e316a5..bb74f0db 100644 --- a/include/toml++/impl/node.hpp +++ b/include/toml++/impl/node.hpp @@ -5,9 +5,12 @@ #pragma once #include "std_utility.hpp" +#include "std_vector.hpp" #include "forward_declarations.hpp" #include "source_region.hpp" #include "header_start.hpp" +#include "preprocessor.hpp" +#include "trivia_piece.hpp" TOML_NAMESPACE_START { @@ -22,6 +25,8 @@ TOML_NAMESPACE_START friend class TOML_PARSER_TYPENAME; source_region source_{}; + std::optional> leading_trivia_; + std::optional> trailing_trivia_; template TOML_NODISCARD @@ -728,6 +733,34 @@ TOML_NAMESPACE_START /// @} + /// \brief Returns the leading trivia attached to this node. + TOML_PURE_INLINE_GETTER + const optional>& leading_trivia() const noexcept + { + return leading_trivia_; + } + + /// \brief Returns the trailing trivia attached to this node. + TOML_PURE_INLINE_GETTER + const optional>& trailing_trivia() const noexcept + { + return trailing_trivia_; + } + + /// \brief Sets the leading trivia attached to this node. + TOML_EXPORTED_MEMBER_FUNCTION + void set_leading_trivia(optional> leading_trivia) noexcept + { + leading_trivia_ = leading_trivia; + } + + /// \brief Sets the trailing trivia attached to this node. + TOML_EXPORTED_MEMBER_FUNCTION + void set_trailing_trivia(optional> trailing_trivia) noexcept + { + trailing_trivia_ = trailing_trivia; + } + private: /// \cond diff --git a/include/toml++/impl/node.inl b/include/toml++/impl/node.inl index 171e05d1..83f91829 100644 --- a/include/toml++/impl/node.inl +++ b/include/toml++/impl/node.inl @@ -29,11 +29,15 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE node::node(node && other) noexcept // - : source_{ std::exchange(other.source_, {}) } + : source_{ std::exchange(other.source_, {}) }, + leading_trivia_(other.leading_trivia_), + trailing_trivia_(other.trailing_trivia_) {} TOML_EXTERNAL_LINKAGE - node::node(const node& /*other*/) noexcept + node::node(const node& other) noexcept + : leading_trivia_(other.leading_trivia_), + trailing_trivia_(other.trailing_trivia_) { // does not copy source information - this is not an error // @@ -41,13 +45,15 @@ TOML_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - node& node::operator=(const node& /*rhs*/) noexcept + node& node::operator=(const node& rhs) noexcept { // does not copy source information - this is not an error // // see https://github.com/marzer/tomlplusplus/issues/49#issuecomment-665089577 source_ = {}; + leading_trivia_ = rhs.leading_trivia_; + trailing_trivia_ = rhs.trailing_trivia_; return *this; } @@ -56,6 +62,8 @@ TOML_NAMESPACE_START { if (&rhs != this) source_ = std::exchange(rhs.source_, {}); + leading_trivia_ = rhs.leading_trivia_; + trailing_trivia_ = rhs.trailing_trivia_; return *this; } diff --git a/include/toml++/impl/parser.hpp b/include/toml++/impl/parser.hpp index c794c5e4..d646ebf4 100644 --- a/include/toml++/impl/parser.hpp +++ b/include/toml++/impl/parser.hpp @@ -30,14 +30,15 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. - /// + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. + /// /// \returns \conditional_return{With exceptions} /// A toml::table. /// \conditional_return{Without exceptions} /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path = {}, bool collect_trivia = false); /// \brief Parses a TOML document from a string view. /// @@ -54,6 +55,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -61,7 +63,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path, bool collect_trivia = false); /// \brief Parses a TOML document from a file. /// @@ -73,6 +75,7 @@ TOML_NAMESPACE_START /// \ecpp /// /// \param file_path The TOML document to parse. Must be valid UTF-8. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -80,7 +83,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::string_view file_path); + parse_result TOML_CALLCONV parse_file(std::string_view file_path, bool collect_trivia = false); #if TOML_HAS_CHAR8 @@ -99,6 +102,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -106,7 +110,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path = {}, bool collect_trivia = false); /// \brief Parses a TOML document from a char8_t string view. /// @@ -123,6 +127,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -130,7 +135,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path, bool collect_trivia = false); /// \brief Parses a TOML document from a file. /// @@ -142,6 +147,7 @@ TOML_NAMESPACE_START /// \ecpp /// /// \param file_path The TOML document to parse. Must be valid UTF-8. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -149,7 +155,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::u8string_view file_path); + parse_result TOML_CALLCONV parse_file(std::u8string_view file_path, bool collect_trivia = false); #endif // TOML_HAS_CHAR8 @@ -172,6 +178,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -179,7 +186,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path, bool collect_trivia = false); /// \brief Parses a TOML document from a stream. /// @@ -201,6 +208,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -208,7 +216,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path, bool collect_trivia = false); /// \brief Parses a TOML document from a file. /// @@ -222,6 +230,7 @@ TOML_NAMESPACE_START /// \ecpp /// /// \param file_path The TOML document to parse. Must be valid UTF-8. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -229,7 +238,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::wstring_view file_path); + parse_result TOML_CALLCONV parse_file(std::wstring_view file_path, bool collect_trivia = false); #endif // TOML_ENABLE_WINDOWS_COMPAT @@ -252,6 +261,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -259,7 +269,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path, bool collect_trivia = false); #endif // TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT @@ -281,6 +291,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -288,7 +299,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path = {}, bool collect_trivia = false); /// \brief Parses a TOML document from a stream. /// @@ -308,6 +319,7 @@ TOML_NAMESPACE_START /// \param source_path The path used to initialize each node's `source().path`. /// If you don't have a path (or you have no intention of using paths in diagnostics) /// then this parameter can safely be left blank. + /// \param collect_trivia Whether to collect trivia such as whitespaces and newlines or not. /// /// \returns \conditional_return{With exceptions} /// A toml::table. @@ -315,7 +327,7 @@ TOML_NAMESPACE_START /// A toml::parse_result. TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path, bool collect_trivia = false); TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS diff --git a/include/toml++/impl/parser.inl b/include/toml++/impl/parser.inl index 6f0136f2..01c29745 100644 --- a/include/toml++/impl/parser.inl +++ b/include/toml++/impl/parser.inl @@ -5,6 +5,7 @@ #pragma once #include "preprocessor.hpp" +#include "trivia_piece.hpp" //# {{ #if !TOML_IMPLEMENTATION #error This is an implementation-only header. @@ -863,6 +864,8 @@ TOML_ANON_NAMESPACE_START std::vector> segments; std::vector starts; std::vector ends; + std::vector> leading_trivias; + std::vector> trailing_trivias; void clear() noexcept { @@ -870,14 +873,25 @@ TOML_ANON_NAMESPACE_START segments.clear(); starts.clear(); ends.clear(); + leading_trivias.clear(); + trailing_trivias.clear(); } - void push_back(std::string_view segment, source_position b, source_position e) + void push_back( + std::string_view segment, + source_position b, + source_position e, + std::optional> leading_trivia, + std::optional> trailing_trivia) { segments.push_back({ buffer.length(), segment.length() }); buffer.append(segment); starts.push_back(b); ends.push_back(e); + if (leading_trivia.has_value()) + leading_trivias.push_back(*leading_trivia); + if (trailing_trivia.has_value()) + trailing_trivias.push_back(*trailing_trivia); } TOML_PURE_INLINE_GETTER @@ -1077,6 +1091,8 @@ TOML_IMPL_NAMESPACE_START std::string string_buffer; std::string recording_buffer; // for diagnostics bool recording = false, recording_whitespace = true; + bool collect_trivia = false; + std::vector trivia_pieces; std::string_view current_scope; size_t nested_values = {}; #if !TOML_EXCEPTIONS @@ -1183,15 +1199,23 @@ TOML_IMPL_NAMESPACE_START { return_if_error_or_eof({}); + std::string piece; bool consumed = false; while (!is_eof() && is_horizontal_whitespace(*cp)) { if TOML_UNLIKELY(!is_ascii_horizontal_whitespace(*cp)) set_error_and_return_default("expected space or tab, saw '"sv, escaped_codepoint{ *cp }, "'"sv); + if (collect_trivia) + piece.append(cp->bytes, cp->count); + consumed = true; advance_and_return_if_error({}); } + + if (collect_trivia && !piece.empty()) + trivia_pieces.push_back(trivia_piece{piece, trivia_type::whitespace}); + return consumed; } @@ -1203,10 +1227,15 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default( R"(vertical tabs '\v' and form-feeds '\f' are not legal line breaks in TOML)"sv); + std::string piece; + if (*cp == U'\r') { advance_and_return_if_error({}); // skip \r + if (collect_trivia) + piece.append(cp->bytes, cp->count); + if TOML_UNLIKELY(is_eof()) set_error_and_return_default("expected '\\n' after '\\r', saw EOF"sv); @@ -1218,6 +1247,11 @@ TOML_IMPL_NAMESPACE_START else if (*cp != U'\n') return false; + if (collect_trivia) { + piece.append(cp->bytes, cp->count); + trivia_pieces.push_back(trivia_piece{piece, trivia_type::whitespace}); + } + advance_and_return_if_error({}); // skip \n return true; } @@ -1248,12 +1282,23 @@ TOML_IMPL_NAMESPACE_START push_parse_scope("comment"sv); + std::string piece; + + if (collect_trivia) + piece.append(cp->bytes, cp->count); + advance_and_return_if_error({}); // skip the '#' while (!is_eof()) { - if (consume_line_break()) + if (consume_line_break()) { + if (collect_trivia) + trivia_pieces.insert( + trivia_pieces.end() - 1, + trivia_piece{piece, trivia_type::comment} + ); return true; + } return_if_error({}); #if TOML_LANG_AT_LEAST(1, 0, 0) @@ -1269,9 +1314,15 @@ TOML_IMPL_NAMESPACE_START "unicode surrogates (U+D800 to U+DFFF) are explicitly prohibited in comments"sv); #endif + if (collect_trivia) + piece.append(cp->bytes, cp->count); + advance_and_return_if_error({}); } + if (collect_trivia) + trivia_pieces.push_back(trivia_piece{piece, trivia_type::comment}); + return true; } @@ -2565,6 +2616,13 @@ TOML_IMPL_NAMESPACE_START else if (*cp == U'_') set_error_and_return_default("values may not begin with underscores"sv); + std::vector leading_trivia; + if (collect_trivia) + { + leading_trivia = trivia_pieces; + trivia_pieces.clear(); + } + const auto begin_pos = cp->position; node_ptr val; @@ -3026,6 +3084,8 @@ TOML_IMPL_NAMESPACE_START } val->source_ = { begin_pos, current_position(1), reader.source_path() }; + if (collect_trivia) + val->leading_trivia_ = leading_trivia; return val; } @@ -3045,6 +3105,12 @@ TOML_IMPL_NAMESPACE_START std::string_view key_segment; const auto key_begin = current_position(); + std::vector leading_trivia; + if (collect_trivia) { + leading_trivia = trivia_pieces; + trivia_pieces.clear(); + } + // bare_key_segment if (is_bare_key_character(*cp)) key_segment = parse_bare_key_segment(); @@ -3083,7 +3149,16 @@ TOML_IMPL_NAMESPACE_START consume_leading_whitespace(); // store segment - key_buffer.push_back(key_segment, key_begin, key_end); + key_buffer.push_back( + key_segment, + key_begin, + key_end, + collect_trivia ? std::optional(leading_trivia) : std::optional>(), + collect_trivia ? std::optional(trivia_pieces) : std::optional>() + ); + + if (collect_trivia) + trivia_pieces.clear(); // eof or no more key to come if (is_eof() || *cp != U'.') @@ -3106,7 +3181,9 @@ TOML_IMPL_NAMESPACE_START return key{ key_buffer[segment_index], - source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path } + source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path }, + collect_trivia ? key_buffer.leading_trivias[segment_index] : optional>(), + collect_trivia ? key_buffer.trailing_trivias[segment_index] : optional>() }; } @@ -3123,6 +3200,9 @@ TOML_IMPL_NAMESPACE_START source_position header_end_pos; bool is_arr = false; + std::vector leading_trivia = trivia_pieces; + trivia_pieces.clear(); + // parse header { // skip first '[' @@ -3186,10 +3266,18 @@ TOML_IMPL_NAMESPACE_START for (size_t i = 0, e = key_buffer.size() - 1u; i < e; i++) { const std::string_view segment = key_buffer[i]; +#if TOML_ENABLE_ORDERED_TABLES + auto pit = parent->find(segment); +#else auto pit = parent->lower_bound(segment); +#endif // parent already existed +#if TOML_ENABLE_ORDERED_TABLES + if (pit != parent->end()) +#else if (pit != parent->end() && pit->first == segment) +#endif { node& p = pit->second; @@ -3240,13 +3328,21 @@ TOML_IMPL_NAMESPACE_START } const auto last_segment = key_buffer.back(); +#if TOML_ENABLE_ORDERED_TABLES + auto it = parent->find(last_segment); +#else auto it = parent->lower_bound(last_segment); +#endif // if there was already a matching node some sanity checking is necessary; // this is ok if we're making an array and the existing element is already an array (new element) // or if we're making a table and the existing element is an implicitly-created table (promote it), // otherwise this is a redefinition error. +#if TOML_ENABLE_ORDERED_TABLES + if (it != parent->end()) +#else if (it != parent->end() && it->first == last_segment) +#endif { node& matching_node = it->second; if (auto arr = matching_node.as_array(); @@ -3279,6 +3375,7 @@ TOML_IMPL_NAMESPACE_START implicit_tables.erase(implicit_tables.cbegin() + (found - implicit_tables.data())); tbl->source_.begin = header_begin_pos; tbl->source_.end = header_end_pos; + tbl->set_leading_trivia(optional>{leading_trivia}); return tbl; } } @@ -3322,6 +3419,7 @@ TOML_IMPL_NAMESPACE_START table& tbl = tbl_arr.emplace_back(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + tbl.set_leading_trivia(optional>{leading_trivia}); return &tbl; } @@ -3331,6 +3429,7 @@ TOML_IMPL_NAMESPACE_START it = parent->emplace_hint
(it, std::move(last_key)); table& tbl = it->second.ref_cast
(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + tbl.set_leading_trivia(optional>{leading_trivia}); return &tbl; } } @@ -3376,10 +3475,18 @@ TOML_IMPL_NAMESPACE_START for (size_t i = 0; i < key_buffer.size() - 1u; i++) { const std::string_view segment = key_buffer[i]; +#if TOML_ENABLE_ORDERED_TABLES + auto pit = tbl->find(segment); +#else auto pit = tbl->lower_bound(segment); +#endif // parent already existed +#if TOML_ENABLE_ORDERED_TABLES + if (pit != tbl->end()) +#else if (pit != tbl->end() && pit->first == segment) +#endif { table* p = pit->second.as_table(); @@ -3413,8 +3520,13 @@ TOML_IMPL_NAMESPACE_START // ensure this isn't a redefinition const std::string_view last_segment = key_buffer.back(); +#if TOML_ENABLE_ORDERED_TABLES + auto it = tbl->find(last_segment); + if (it != tbl->end()) +#else auto it = tbl->lower_bound(last_segment); if (it != tbl->end() && it->first == last_segment) +#endif { set_error("cannot redefine existing "sv, to_sv(it->second.type()), @@ -3484,6 +3596,11 @@ TOML_IMPL_NAMESPACE_START root.source_.end = eof_pos; if (current_table && current_table != &root && current_table->source_.end <= current_table->source_.begin) current_table->source_.end = eof_pos; + if (collect_trivia) + { + current_table->trailing_trivia_ = trivia_pieces; + trivia_pieces.clear(); + } } static void update_region_ends(node& nde) noexcept @@ -3523,9 +3640,14 @@ TOML_IMPL_NAMESPACE_START } public: - parser(utf8_reader_interface&& reader_) // + parser(utf8_reader_interface&& reader_) + : parser(std::move(reader_), false) + {} + + parser(utf8_reader_interface&& reader_, bool collect_trivia) : reader{ reader_ } { + this->collect_trivia = collect_trivia; root.source_ = { prev_pos, prev_pos, reader.source_path() }; if (!reader.peek_eof()) @@ -3598,6 +3720,11 @@ TOML_IMPL_NAMESPACE_START if (prev == parse_type::val) { prev = parse_type::comma; + if (collect_trivia) + { + arr.back().set_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } advance_and_return_if_error_or_eof({}); continue; } @@ -3630,6 +3757,14 @@ TOML_IMPL_NAMESPACE_START } } + if (collect_trivia) + { + if (prev == parse_type::comma) + trivia_pieces.insert(trivia_pieces.begin(), trivia_piece{",", trivia_type::trailing_comma}); + arr.set_inner_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } + return_if_error({}); return arr_ptr; } @@ -3678,6 +3813,11 @@ TOML_IMPL_NAMESPACE_START if (prev == parse_type::kvp) { prev = parse_type::comma; + if (collect_trivia) + { + tbl.last_inserted().value()->second.set_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } advance_and_return_if_error_or_eof({}); } else @@ -3687,9 +3827,9 @@ TOML_IMPL_NAMESPACE_START // closing '}' else if (*cp == U'}') { - if constexpr (!TOML_LANG_UNRELEASED) // toml/issues/516 (newlines/trailing commas in inline tables) + if (prev == parse_type::comma) { - if (prev == parse_type::comma) + if constexpr (!TOML_LANG_UNRELEASED) // toml/issues/516 (newlines/trailing commas in inline tables) { set_error_and_return_default("expected key-value pair, saw closing '}' (dangling comma)"sv); continue; @@ -3716,6 +3856,14 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("expected key or closing '}', saw '"sv, to_sv(*cp), "'"sv); } + if (collect_trivia) + { + if (prev == parse_type::comma) + trivia_pieces.insert(trivia_pieces.begin(), trivia_piece{ ",", trivia_type::trailing_comma }); + tbl.set_inner_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } + return_if_error({}); return tbl_ptr; } @@ -3755,14 +3903,14 @@ TOML_ANON_NAMESPACE_START { TOML_NODISCARD TOML_INTERNAL_LINKAGE - parse_result do_parse(utf8_reader_interface && reader) + parse_result do_parse(utf8_reader_interface && reader, bool collect_trivia) { - return impl::parser{ std::move(reader) }; + return impl::parser{ std::move(reader), collect_trivia }; } TOML_NODISCARD TOML_INTERNAL_LINKAGE - parse_result do_parse_file(std::string_view file_path) + parse_result do_parse_file(std::string_view file_path, bool collect_trivia) { #if TOML_EXCEPTIONS #define TOML_PARSE_FILE_ERROR(msg, path) \ @@ -3805,12 +3953,12 @@ TOML_ANON_NAMESPACE_START std::vector file_data; file_data.resize(static_cast(file_size)); file.read(file_data.data(), static_cast(file_size)); - return parse(std::string_view{ file_data.data(), file_data.size() }, std::move(file_path_str)); + return parse(std::string_view{ file_data.data(), file_data.size() }, std::move(file_path_str), collect_trivia); } // otherwise parse it using the streams else - return parse(file, std::move(file_path_str)); + return parse(file, std::move(file_path_str), collect_trivia); #undef TOML_PARSE_FILE_ERROR } @@ -3822,56 +3970,56 @@ TOML_NAMESPACE_START TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::string_view file_path) + parse_result TOML_CALLCONV parse_file(std::string_view file_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse_file(file_path); + return TOML_ANON_NAMESPACE::do_parse_file(file_path, collect_trivia); } #if TOML_HAS_CHAR8 TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::u8string_view file_path) + parse_result TOML_CALLCONV parse_file(std::u8string_view file_path, bool collect_trivia) { std::string file_path_str; file_path_str.resize(file_path.length()); memcpy(file_path_str.data(), file_path.data(), file_path.length()); - return TOML_ANON_NAMESPACE::do_parse_file(file_path_str); + return TOML_ANON_NAMESPACE::do_parse_file(file_path_str, collect_trivia); } #endif // TOML_HAS_CHAR8 @@ -3879,21 +4027,21 @@ TOML_NAMESPACE_START #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::wstring_view file_path) + parse_result TOML_CALLCONV parse_file(std::wstring_view file_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse_file(impl::narrow(file_path)); + return TOML_ANON_NAMESPACE::do_parse_file(impl::narrow(file_path), collect_trivia); } #endif // TOML_ENABLE_WINDOWS_COMPAT @@ -3901,9 +4049,9 @@ TOML_NAMESPACE_START #if TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } #endif // TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT diff --git a/include/toml++/impl/preprocessor.hpp b/include/toml++/impl/preprocessor.hpp index feeb802d..7fa83e0f 100644 --- a/include/toml++/impl/preprocessor.hpp +++ b/include/toml++/impl/preprocessor.hpp @@ -1211,6 +1211,42 @@ TOML_ENABLE_WARNINGS; /// \detail Defaults to `0`. //# }} +#ifndef TOML_ENABLE_ORDERED_TABLES +#define TOML_ENABLE_ORDERED_TABLES 0 +#endif +//# {{ +/// \def TOML_ENABLE_ORDERED_TABLES +/// \brief Make tables retain insertion order instead of lexicographic order. +/// \detail Defaults to `0`. +//# }} + +#ifndef TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA +#define TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA 0 +#endif +//# {{ +/// \def TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA +/// \brief Disable using noexcept() in lambda definitions within the toml++ library implementation. +/// \detail This macro offers a workaround to a bug in the old "legacy lambda processor" of Visual C++, which +/// caused compile errors like "error C2057: expected constant expression", when it encountered such lambda's. +/// These compile errors were reported by Kevin Dick, Jan 19, 2024, at https://github.com/marzer/tomlplusplus/issues/219 +//# }} + +#ifndef TOML_DISABLE_NOEXCEPT_NOEXCEPT +#define TOML_DISABLE_NOEXCEPT_NOEXCEPT 0 + #ifdef _MSC_VER + #if _MSC_VER <= 1943 // Up to Visual Studio 2022 Version 17.13.6 + #undef TOML_DISABLE_NOEXCEPT_NOEXCEPT + #define TOML_DISABLE_NOEXCEPT_NOEXCEPT 1 + #endif + #endif +#endif +//# {{ +/// \def TOML_DISABLE_NOEXCEPT_NOEXCEPT +/// \brief Disable using noexcept(noexcept()) within the toml++ library implementation. +/// \detail This macro offers a workaround to a bug in Visual C++ (Visual Studio 2022), which caused +/// compile errors, saying: "error C3878: syntax error: unexpected token ',' following 'simple-type-specifier'" +//# }} + /// @} //#==================================================================================================================== //# CHARCONV SUPPORT diff --git a/include/toml++/impl/std_list.hpp b/include/toml++/impl/std_list.hpp new file mode 100644 index 00000000..1d4ccdb6 --- /dev/null +++ b/include/toml++/impl/std_list.hpp @@ -0,0 +1,10 @@ +//# This file is a part of toml++ and is subject to the the terms of the MIT license. +//# Copyright (c) Mark Gillard +//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT +#pragma once + +#include "preprocessor.hpp" +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; diff --git a/include/toml++/impl/table.hpp b/include/toml++/impl/table.hpp index edb73166..88540377 100644 --- a/include/toml++/impl/table.hpp +++ b/include/toml++/impl/table.hpp @@ -6,12 +6,15 @@ #include "forward_declarations.hpp" #include "std_map.hpp" +#include "std_list.hpp" #include "std_initializer_list.hpp" #include "array.hpp" #include "make_node.hpp" #include "node_view.hpp" #include "key.hpp" #include "header_start.hpp" +#include "preprocessor.hpp" +#include /// \cond TOML_IMPL_NAMESPACE_START @@ -33,9 +36,16 @@ TOML_IMPL_NAMESPACE_START friend class table_iterator; using proxy_type = table_proxy_pair; + +#if TOML_ENABLE_ORDERED_TABLES + using mutable_map_iterator = std::list>::iterator; + using const_map_iterator = std::list>::const_iterator; +#else using mutable_map_iterator = std::map>::iterator; using const_map_iterator = std::map>::const_iterator; - using map_iterator = std::conditional_t; +#endif + + using map_iterator = std::conditional_t; mutable map_iterator iter_; alignas(proxy_type) mutable unsigned char proxy_[sizeof(proxy_type)]; @@ -221,13 +231,25 @@ TOML_NAMESPACE_START private: /// \cond - using map_type = std::map>; +#if TOML_ENABLE_ORDERED_TABLES + using map_pair = std::pair; + using entries_type = std::list>; + using map_type = std::unordered_map; + map_type map_; + entries_type entries_; + using map_iterator = typename entries_type::iterator; + using const_map_iterator = typename entries_type::const_iterator; +#else using map_pair = std::pair; + using map_type = std::map>; using map_iterator = typename map_type::iterator; using const_map_iterator = typename map_type::const_iterator; map_type map_; + optional last_inserted_; +#endif bool inline_ = false; + optional> inner_trailing_trivia_; TOML_NODISCARD_CTOR TOML_EXPORTED_MEMBER_FUNCTION @@ -284,6 +306,20 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION table& operator=(table&& rhs) noexcept; + /// \brief Gets the inner trailing trivia. + TOML_CONST_INLINE_GETTER + const optional> inner_trailing_trivia() const noexcept + { + return inner_trailing_trivia_; + } + + /// \brief Sets the inner trailing trivia. + TOML_EXPORTED_MEMBER_FUNCTION + void set_inner_trailing_trivia(optional> trivia) noexcept + { + inner_trailing_trivia_ = trivia; + } + /// \name Type checks /// @{ @@ -792,46 +828,85 @@ TOML_NAMESPACE_START /// \brief A BidirectionalIterator for iterating over const key-value pairs in a toml::table. using const_iterator = toml::const_table_iterator; + TOML_EXPORTED_MEMBER_FUNCTION + optional last_inserted() noexcept + { + if (size() > 0) + { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ --entries_.end() }; +#else + return last_inserted_; +#endif + } + else + return std::nullopt; + } + /// \brief Returns an iterator to the first key-value pair. TOML_PURE_INLINE_GETTER iterator begin() noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ entries_.begin() }; +#else return iterator{ map_.begin() }; +#endif } /// \brief Returns an iterator to the first key-value pair. TOML_PURE_INLINE_GETTER const_iterator begin() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cbegin() }; +#else return const_iterator{ map_.cbegin() }; +#endif } /// \brief Returns an iterator to the first key-value pair. TOML_PURE_INLINE_GETTER const_iterator cbegin() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cbegin() }; +#else return const_iterator{ map_.cbegin() }; +#endif } /// \brief Returns an iterator to one-past-the-last key-value pair. TOML_PURE_INLINE_GETTER iterator end() noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ entries_.end() }; +#else return iterator{ map_.end() }; +#endif } /// \brief Returns an iterator to one-past-the-last key-value pair. TOML_PURE_INLINE_GETTER const_iterator end() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cend() }; +#else return const_iterator{ map_.cend() }; +#endif } /// \brief Returns an iterator to one-past-the-last key-value pair. TOML_PURE_INLINE_GETTER const_iterator cend() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cend() }; +#else return const_iterator{ map_.cend() }; +#endif } private: @@ -891,7 +966,11 @@ TOML_NAMESPACE_START using kvp_type = impl::copy_cv>; +#if TOML_ENABLE_ORDERED_TABLES + for (kvp_type& kvp : tbl.entries_) +#else for (kvp_type& kvp : tbl.map_) +#endif { using node_ref = impl::copy_cvref; static_assert(std::is_reference_v); @@ -1129,13 +1208,16 @@ TOML_NAMESPACE_START private: /// \cond - TOML_PURE_GETTER - TOML_EXPORTED_MEMBER_FUNCTION - map_iterator get_lower_bound(std::string_view) noexcept; +#if !TOML_ENABLE_ORDERED_TABLES + TOML_PURE_GETTER + TOML_EXPORTED_MEMBER_FUNCTION + map_iterator get_lower_bound(std::string_view) noexcept; +#endif /// \endcond public: +#if !TOML_ENABLE_ORDERED_TABLES /// \brief Returns an iterator to the first key-value pair with key that is _not less_ than the given key. /// /// \returns An iterator to the first matching key-value pair, or #end(). @@ -1153,6 +1235,7 @@ TOML_NAMESPACE_START { return const_iterator{ const_cast(*this).get_lower_bound(key) }; } +#endif // !TOML_ENABLE_ORDERED_TABLES #if TOML_ENABLE_WINDOWS_COMPAT @@ -1499,6 +1582,28 @@ TOML_NAMESPACE_START "ValueType argument of table::emplace_hint() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); +#if TOML_ENABLE_ORDERED_TABLES + auto toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + if constexpr (moving_node_ptr) + entries_.push_back(std::pair{ toml_key, + std::move(static_cast(args)...) }); + else + { + entries_.push_back(std::pair{ toml_key, + new impl::wrap_node{ static_cast(args)... } }); + } + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return iterator{ entry_ipos }; + } + else + { + return iterator{ ipos->second }; + } +#else map_iterator ipos = insert_with_hint(hint, toml::key{ static_cast(key) }, nullptr); // if second is nullptr then we successully claimed the key and inserted the empty sentinel, @@ -1526,6 +1631,7 @@ TOML_NAMESPACE_START } } return iterator{ ipos }; +#endif } } @@ -1607,6 +1713,18 @@ TOML_NAMESPACE_START } else { +#if TOML_ENABLE_ORDERED_TABLES + auto toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + auto table_ipos = insert_with_hint(iterator{ ipos->second }, + toml::key{ static_cast(key) }, + impl::make_node(static_cast(val), flags)); + return { iterator{ table_ipos }, true }; + } + return { iterator { ipos->second }, false }; +#else const auto key_view = std::string_view{ key }; map_iterator ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -1617,6 +1735,7 @@ TOML_NAMESPACE_START return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; +#endif } } @@ -1754,6 +1873,22 @@ TOML_NAMESPACE_START } else { +#if TOML_ENABLE_ORDERED_TABLES + toml::key toml_key = toml::key{ static_cast(key) }; + map_type::iterator ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + entries_.push_back(std::pair{ toml_key, impl::make_node(static_cast(val), flags) }); + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return { iterator{ entry_ipos }, true }; + } + else + { + ipos->second->second = impl::make_node(static_cast(val), flags); + return { iterator{ ipos->second }, false }; + } +#else const auto key_view = std::string_view{ key }; map_iterator ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -1768,6 +1903,7 @@ TOML_NAMESPACE_START (*ipos).second = impl::make_node(static_cast(val), flags); return { iterator{ ipos }, false }; } +#endif } } @@ -1838,6 +1974,22 @@ TOML_NAMESPACE_START "ValueType argument of table::emplace() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); +#if TOML_ENABLE_ORDERED_TABLES + toml::key toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + entries_.push_back( + { + toml_key, + impl::node_ptr{ new impl::wrap_node{ static_cast(args)... } } + }); + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return { iterator{ entry_ipos }, true }; + } + return { iterator{ ipos->second }, false }; +#else const auto key_view = std::string_view{ key }; auto ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -1849,6 +2001,7 @@ TOML_NAMESPACE_START return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; +#endif } } diff --git a/include/toml++/impl/table.inl b/include/toml++/impl/table.inl index 01cba016..0d12dc72 100644 --- a/include/toml++/impl/table.inl +++ b/include/toml++/impl/table.inl @@ -52,17 +52,36 @@ TOML_NAMESPACE_START if (!b->value) // empty node_views continue; +#if TOML_ENABLE_ORDERED_TABLES + entries_.push_back({ std::move(b->key), std::move(b->value) }); + map_.insert_or_assign(std::move(b->key), std::prev(entries_.end())); +#else map_.insert_or_assign(std::move(b->key), std::move(b->value)); +#endif } } TOML_EXTERNAL_LINKAGE table::table(const table& other) // : node(other), - inline_{ other.inline_ } + inline_{ other.inline_ }, +#if !TOML_ENABLE_ORDERED_TABLES + last_inserted_(other.last_inserted_), +#endif + inner_trailing_trivia_(other.inner_trailing_trivia_) { +#if TOML_ENABLE_ORDERED_TABLES + for (auto&& [k, v] : other.entries_) + { + entries_.push_back({ k, impl::make_node(*v) }); + map_.emplace(k, std::prev(entries_.end())); + } +#else for (auto&& [k, v] : other.map_) + { map_.emplace_hint(map_.end(), k, impl::make_node(*v)); + } +#endif #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -73,7 +92,13 @@ TOML_NAMESPACE_START table::table(table && other) noexcept // : node(std::move(other)), map_{ std::move(other.map_) }, - inline_{ other.inline_ } +#if TOML_ENABLE_ORDERED_TABLES + entries_{ std::move(other.entries_) }, +#else + last_inserted_(other.last_inserted_), +#endif + inline_{ other.inline_ }, + inner_trailing_trivia_(other.inner_trailing_trivia_) { #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -87,9 +112,22 @@ TOML_NAMESPACE_START { node::operator=(rhs); map_.clear(); +#if TOML_ENABLE_ORDERED_TABLES + entries_.clear(); + for (auto&& [k, v] : rhs.entries_) + { + entries_.push_back({ k, impl::make_node(*v) }); + map_.emplace(k, std::prev(entries_.end())); + } +#else for (auto&& [k, v] : rhs.map_) + { map_.emplace_hint(map_.end(), k, impl::make_node(*v)); + } + last_inserted_ = rhs.last_inserted_; +#endif inline_ = rhs.inline_; + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } @@ -102,6 +140,10 @@ TOML_NAMESPACE_START node::operator=(std::move(rhs)); map_ = std::move(rhs.map_); inline_ = rhs.inline_; + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; +#if !TOML_ENABLE_ORDERED_TABLES + last_inserted_ = rhs.last_inserted_; +#endif } return *this; } @@ -113,10 +155,17 @@ TOML_NAMESPACE_START if (map_.empty()) return false; +#if TOML_ENABLE_ORDERED_TABLES + if (ntype == node_type::none) + ntype = entries_.cbegin()->second->type(); + + for (auto&& [k, v] : entries_) +#else if (ntype == node_type::none) ntype = map_.cbegin()->second->type(); for (auto&& [k, v] : map_) +#endif { TOML_UNUSED(k); if (v->type() != ntype) @@ -135,9 +184,19 @@ TOML_NAMESPACE_START first_nonmatch = {}; return false; } + if (ntype == node_type::none) +#if TOML_ENABLE_ORDERED_TABLES + ntype = entries_.cbegin()->second->type(); +#else ntype = map_.cbegin()->second->type(); +#endif + +#if TOML_ENABLE_ORDERED_TABLES + for (const auto& [k, v] : entries_) +#else for (const auto& [k, v] : map_) +#endif { TOML_UNUSED(k); if (v->type() != ntype) @@ -163,8 +222,12 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE node* table::get(std::string_view key) noexcept { - if (auto it = map_.find(key); it != map_.end()) + if (auto it = map_.find(toml::key{ key }); it != map_.end()) +#if TOML_ENABLE_ORDERED_TABLES + return it->second->second.get(); +#else return it->second.get(); +#endif return nullptr; } @@ -192,48 +255,86 @@ TOML_NAMESPACE_START return *n; } +#if !TOML_ENABLE_ORDERED_TABLES TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::map_iterator table::get_lower_bound(std::string_view key) noexcept { return map_.lower_bound(key); } +#endif // !TOML_ENABLE_ORDERED_TABLES TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::iterator table::find(std::string_view key) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + auto ipos = map_.find(toml::key{ key }); + if (ipos == map_.end()) + { + return iterator{ entries_.end() }; + } + return iterator{ ipos->second }; +#else return iterator{ map_.find(key) }; +#endif } TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::const_iterator table::find(std::string_view key) const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ map_.find(toml::key{ key })->second }; +#else return const_iterator{ map_.find(key) }; +#endif } TOML_EXTERNAL_LINKAGE table::map_iterator table::erase(const_map_iterator pos) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + map_.erase(pos->first); + return entries_.erase(pos); +#else return map_.erase(pos); +#endif } TOML_EXTERNAL_LINKAGE table::map_iterator table::erase(const_map_iterator begin, const_map_iterator end) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + for (auto ipos = begin; ipos != end; ipos++) { + map_.erase(begin->first); + } + return entries_.erase(begin, end); +#else return map_.erase(begin, end); +#endif } TOML_EXTERNAL_LINKAGE size_t table::erase(std::string_view key) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + size_t result = map_.erase(toml::key{ key }); + auto ipos = map_.find(toml::key{ key }); + if (ipos != map_.end()) + { + map_.erase(ipos); + entries_.erase(ipos->second); + } + return result; +#else if (auto it = map_.find(key); it != map_.end()) { map_.erase(it); return size_t{ 1 }; } return size_t{}; +#endif } TOML_EXTERNAL_LINKAGE @@ -244,7 +345,11 @@ TOML_NAMESPACE_START for (auto it = map_.begin(); it != map_.end();) { +#if TOML_ENABLE_ORDERED_TABLES + if (auto arr = it->second->second->as_array()) +#else if (auto arr = it->second->as_array()) +#endif { if (recursive) arr->prune(true); @@ -255,7 +360,11 @@ TOML_NAMESPACE_START continue; } } +#if TOML_ENABLE_ORDERED_TABLES + else if (auto tbl = it->second->second->as_table()) +#else else if (auto tbl = it->second->as_table()) +#endif { if (recursive) tbl->prune(true); @@ -281,7 +390,26 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE table::map_iterator table::insert_with_hint(const_iterator hint, key && k, impl::node_ptr && v) { - return map_.emplace_hint(const_map_iterator{ hint }, std::move(k), std::move(v)); +#if TOML_ENABLE_ORDERED_TABLES + auto ipos = map_.find(k); + if (ipos == map_.end()) + { + entries_.emplace_back(std::pair{ k, std::move(v) }); + auto entry_ipos = std::prev(entries_.end()); + map_.emplace(std::move(k), entry_ipos); + return entry_ipos; + } + else + { + return ipos->second; + } +#else + auto prev_size = map_.size(); + auto ipos = map_.emplace_hint(const_map_iterator{ hint }, std::move(k), std::move(v)); + if (map_.size() > prev_size) + last_inserted_ = toml::table_iterator { ipos }; + return ipos; +#endif } TOML_PURE_GETTER @@ -293,7 +421,11 @@ TOML_NAMESPACE_START if (lhs.map_.size() != rhs.map_.size()) return false; +#if TOML_ENABLE_ORDERED_TABLES + for (auto l = lhs.entries_.begin(), r = rhs.entries_.begin(), e = lhs.entries_.end(); l != e; l++, r++) +#else for (auto l = lhs.map_.begin(), r = rhs.map_.begin(), e = lhs.map_.end(); l != e; l++, r++) +#endif { if (l->first != r->first) return false; diff --git a/include/toml++/impl/toml_formatter.hpp b/include/toml++/impl/toml_formatter.hpp index ffd63255..c80c7122 100644 --- a/include/toml++/impl/toml_formatter.hpp +++ b/include/toml++/impl/toml_formatter.hpp @@ -5,6 +5,7 @@ #pragma once #include "preprocessor.hpp" +#include "trivia_piece.hpp" #if TOML_ENABLE_FORMATTERS #include "std_vector.hpp" @@ -65,6 +66,17 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION void print(const toml::table&); + TOML_EXPORTED_MEMBER_FUNCTION + void print_trivia(const std::vector& trivia); + + using impl::formatter::print_value; + + TOML_EXPORTED_MEMBER_FUNCTION + void print_value(const node& node); + + TOML_EXPORTED_MEMBER_FUNCTION + void print_kvp(const key& k, const node& v); + TOML_EXPORTED_MEMBER_FUNCTION void print(); diff --git a/include/toml++/impl/toml_formatter.inl b/include/toml++/impl/toml_formatter.inl index e764448b..53bdb0aa 100644 --- a/include/toml++/impl/toml_formatter.inl +++ b/include/toml++/impl/toml_formatter.inl @@ -5,6 +5,8 @@ #pragma once #include "preprocessor.hpp" +#include "formatter.hpp" +#include "forward_declarations.hpp" //# {{ #if !TOML_IMPLEMENTATION #error This is an implementation-only header. @@ -130,52 +132,128 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE void toml_formatter::print(const key& k) { + if (preserve_source_trivia() && k.leading_trivia().has_value()) + print_trivia(*k.leading_trivia()); print_string(k.str(), false, true, false); + if (preserve_source_trivia() && k.trailing_trivia().has_value()) + print_trivia(*k.trailing_trivia()); + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_trivia(const std::vector& trivia) + { + for (auto & piece : trivia) { + print_unformatted(piece.text); + } + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_value(const node& node) + { + const auto type = node.type(); + TOML_ASSUME(type != node_type::none); + switch (type) + { + case node_type::table: print_inline(*reinterpret_cast(&node)); break; + case node_type::array: print(*reinterpret_cast(&node)); break; + default: + if (preserve_source_trivia() && node.leading_trivia().has_value()) + print_trivia(node.leading_trivia().value()); + this->impl::formatter::print_value(node, type); + if (preserve_source_trivia() && node.trailing_trivia().has_value()) + print_trivia(node.trailing_trivia().value()); + } } TOML_EXTERNAL_LINKAGE void toml_formatter::print_inline(const table& tbl) { + if (preserve_source_trivia() && tbl.leading_trivia().has_value()) + print_trivia(tbl.leading_trivia().value()); + if (tbl.empty()) { - print_unformatted("{}"sv); + print_unformatted("{"sv); + if (preserve_source_trivia() && tbl.inner_trailing_trivia().has_value()) + print_trivia(tbl.inner_trailing_trivia().value()); + print_unformatted("}"sv); + if (preserve_source_trivia() && tbl.trailing_trivia().has_value()) + print_trivia(tbl.trailing_trivia().value()); return; } - print_unformatted("{ "sv); + if (preserve_source_trivia()) + print_unformatted("{"sv); + else + print_unformatted("{ "sv); bool first = false; for (auto&& [k, v] : tbl) { if (first) - print_unformatted(", "sv); + { + if (preserve_source_trivia()) + print_unformatted(","sv); + else + print_unformatted(", "sv); + } first = true; - print(k); - if (terse_kvps()) - print_unformatted("="sv); - else - print_unformatted(" = "sv); + print_kvp(k, v); + } - const auto type = v.type(); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); - } + if (preserve_source_trivia()) + { + if (tbl.inner_trailing_trivia().has_value()) + print_trivia(tbl.inner_trailing_trivia().value()); + print_unformatted("}"sv); } + else + print_unformatted(" }"sv); - print_unformatted(" }"sv); + if (preserve_source_trivia() && tbl.trailing_trivia().has_value()) + print_trivia(tbl.trailing_trivia().value()); + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_kvp(const key& k, const node& v) + { + print(k); + if (terse_kvps()) + print_unformatted("="sv); + else if (!preserve_source_trivia()) + print_unformatted(" = "sv); + else + { + if (!k.trailing_trivia().has_value()) + print_unformatted(" "sv); + print_unformatted("="sv); + if (!v.leading_trivia().has_value()) + print_unformatted(" "sv); + } + + print_value(v); } TOML_EXTERNAL_LINKAGE void toml_formatter::print(const array& arr) { + if (preserve_source_trivia() && arr.leading_trivia().has_value()) + print_trivia(arr.leading_trivia().value()); + if (arr.empty()) { - print_unformatted("[]"sv); + if (preserve_source_trivia() && arr.inner_trailing_trivia().has_value()) + { + print_unformatted("["sv); + print_trivia(arr.inner_trailing_trivia().value()); + print_unformatted("]"sv); + } + else + print_unformatted("[]"sv); + + if (preserve_source_trivia() && arr.trailing_trivia().has_value()) + print_trivia(arr.trailing_trivia().value()); return; } @@ -194,44 +272,45 @@ TOML_NAMESPACE_START if (indent_array_elements()) increase_indent(); } - else - print_unformatted(' '); for (size_t i = 0; i < arr.size(); i++) { if (i > 0u) - { print_unformatted(','); - if (!multiline) + + auto& v = arr[i]; + if (!preserve_source_trivia() || !v.leading_trivia().has_value()) + { + if (multiline) + { + print_newline(true); + print_indent(); + } + else print_unformatted(' '); } + print_value(v); + } + + if (preserve_source_trivia() && arr.inner_trailing_trivia().has_value()) + print_trivia(arr.inner_trailing_trivia().value()); + else + { if (multiline) { + indent(original_indent); print_newline(true); print_indent(); } - - auto& v = arr[i]; - const auto type = v.type(); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); - } - } - if (multiline) - { - indent(original_indent); - print_newline(true); - print_indent(); + else + print_unformatted(' '); } - else - print_unformatted(' '); print_unformatted("]"sv); + + if (preserve_source_trivia() && arr.trailing_trivia().has_value()) + print_trivia(arr.trailing_trivia().value()); } TOML_EXTERNAL_LINKAGE @@ -255,20 +334,11 @@ TOML_NAMESPACE_START continue; pending_table_separator_ = true; - print_newline(); - print_indent(); - print(k); - if (terse_kvps()) - print_unformatted("="sv); - else - print_unformatted(" = "sv); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); + if (!preserve_source_trivia() || !k.leading_trivia().has_value()) { + print_newline(); + print_indent(); } + print_kvp(k, v); } const auto print_key_path = [&]() @@ -378,6 +448,9 @@ TOML_NAMESPACE_START if (dump_failed_parse_result()) return; + if (preserve_source_trivia() && source().leading_trivia().has_value()) + print_trivia(source().leading_trivia().value()); + switch (auto source_type = source().type()) { case node_type::table: @@ -397,6 +470,9 @@ TOML_NAMESPACE_START default: print_value(source(), source_type); } + + if (preserve_source_trivia() && source().trailing_trivia().has_value()) + print_trivia(source().trailing_trivia().value()); } } TOML_NAMESPACE_END; diff --git a/include/toml++/impl/trivia_piece.hpp b/include/toml++/impl/trivia_piece.hpp new file mode 100644 index 00000000..2c8aa854 --- /dev/null +++ b/include/toml++/impl/trivia_piece.hpp @@ -0,0 +1,31 @@ +//# This file is a part of toml++ and is subject to the the terms of the MIT license. +//# Copyright (c) Mark Gillard +//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT +#pragma once + +#include "std_string.hpp" +#include "preprocessor.hpp" + +TOML_NAMESPACE_START +{ + /// \brief The type of a piece of TOML trivia. + enum class TOML_CLOSED_ENUM trivia_type : uint8_t + { + whitespace, + comment, + trailing_comma + }; + + /// \brief TOML trivia piece. + /// + /// \detail The trivia between two nodes can be separated into individual trivia pieces such as whitespace and comments. + struct trivia_piece + { + /// \brief The text representing the trivia. + std::string text; + /// \brief The type of trivia. + trivia_type type; + }; +} +TOML_NAMESPACE_END; diff --git a/meson.build b/meson.build index 46e46c6b..1127af9a 100644 --- a/meson.build +++ b/meson.build @@ -39,6 +39,7 @@ is_pedantic = get_option('pedantic') or is_devel is_windows = host_machine.system() == 'windows' is_x64 = host_machine.cpu_family() == 'x86_64' is_subproject = meson.is_subproject() +enable_ordered_tables = get_option('ordered_tables') cpp = meson.get_compiler('cpp') is_gcc = cpp.get_id() == 'gcc' @@ -83,6 +84,9 @@ global_args = cpp.get_supported_arguments( '/Zc:externConstexpr', '/Zc:preprocessor' ) +if enable_ordered_tables + global_args += cpp.get_supported_arguments('-DTOML_ENABLE_ORDERED_TABLES=1') +endif if has_exceptions global_args += cpp.get_supported_arguments('/Zc:throwingNew', '-D_HAS_EXCEPTIONS=1') else diff --git a/meson_options.txt b/meson_options.txt index 817b874d..16e17aff 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,6 +6,7 @@ option('build_tt', type: 'boolean', value: false, description: 'Enable to bui option('pedantic', type: 'boolean', value: false, description: 'Enable as many compiler warnings as possible (default: false) (implied by devel)') option('time_trace', type: 'boolean', value: false, description: 'Enable the -ftime-trace option (Clang only)') option('unreleased_features', type: 'boolean', value: false, description: 'Enable TOML_UNRELEASED_FEATURES=1 (default: false) (only relevant when compiling the library)') +option('ordered_tables', type: 'boolean', value: false, description: 'Make tables ordered (default: false)') option('generate_cmake_config', type: 'boolean', value: true, description: 'Generate a cmake package config file (default: true - no effect when included as a subproject)') option('use_vendored_libs', type: 'boolean', value: true, description: 'Use the libs from the vendor dir when building tests.') diff --git a/tests/meson.build b/tests/meson.build index 41329788..eb923bfb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -29,6 +29,7 @@ test_sources = files( 'parsing_spec_example.cpp', 'parsing_strings.cpp', 'parsing_tables.cpp', + 'parsing_trivia.cpp', 'path.cpp', 'tests.cpp', 'user_feedback.cpp', diff --git a/tests/parsing_trivia.cpp b/tests/parsing_trivia.cpp new file mode 100644 index 00000000..88b93556 --- /dev/null +++ b/tests/parsing_trivia.cpp @@ -0,0 +1,76 @@ +// This file is a part of toml++ and is subject to the the terms of the MIT license. +// Copyright (c) Mark Gillard +// See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text. +// SPDX-License-Identifier: MIT + +#include "tests.hpp" +#include "toml++/impl/forward_declarations.hpp" +#include "toml++/impl/toml_formatter.hpp" +#include + +TEST_CASE("parsing - trivia") +{ + parsing_should_succeed( + FILE_LINE_ARGS, + R"( +integers = [ 1, 2, 3 ] +integers2 = [ + 1, 2, 3, +] +integers3 = [ + 1, + 2, # this is ok +] +# Colors? +colors = [ "red", "yellow", "green" ] +nested_array_of_int = [ [ 1, 2 ], [3, 4, 5] , ] +nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] +string_array = [ "all", 'strings', """are the same""", '''type''' ] +inline_table = {number =1 , "string"= 'hi', array=[ 1,2, 3] } + +[asdf] +key = "value" + +)"sv, + [](table&& tbl) + { + tbl.for_each( + [](const toml::key& key, auto&& val) + { + CHECK(key.leading_trivia().has_value()); + auto vector = key.leading_trivia(); + + std::cout << "Key: " << key.str() << "\n"; + for (auto & trivia_piece : *vector) + { + if (trivia_piece.type == trivia_type::whitespace) + std::cout << "whitespace: '"; + else + std::cout << "comment: '"; + std::cout << trivia_piece.text; + std::cout << "'\n"; + } + std::cout << "\n"; + + if (val.as_array() != nullptr) + { + std::cout << "Value[0]:\n"; + for (auto & trivia_piece : *val.as_array()->leading_trivia()) + { + if (trivia_piece.type == trivia_type::whitespace) + std::cout << "whitespace: '"; + else + std::cout << "comment: '"; + std::cout << trivia_piece.text; + std::cout << "'\n"; + } + } + std::cout << "\n"; + }); + + std::cout << "Formatted:\n"; + std::cout << toml_formatter(tbl, toml_formatter::default_flags | format_flags::preserve_source_trivia); + }, + {}, + true); +} diff --git a/tests/tests.cpp b/tests/tests.cpp index 1ba88057..f8eb8acd 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -9,7 +9,8 @@ bool parsing_should_succeed(std::string_view test_file, uint32_t test_line, std::string_view toml_str, pss_func&& func, - std::string_view source_path) + std::string_view source_path, + bool collect_trivia) { INFO("["sv << test_file << ", line "sv << test_line << "] "sv << "parsing_should_succeed(\""sv << toml_str << "\")"sv) @@ -36,18 +37,18 @@ bool parsing_should_succeed(std::string_view test_file, { INFO("Parsing string directly"sv) if (func) - func(validate_table(toml::parse(toml_str, source_path), source_path)); + func(validate_table(toml::parse(toml_str, source_path, collect_trivia), source_path)); else - validate_table(toml::parse(toml_str, source_path), source_path); + validate_table(toml::parse(toml_str, source_path, collect_trivia), source_path); } { INFO("Parsing from a string stream"sv) std::stringstream ss; ss.write(toml_str.data(), static_cast(toml_str.length())); if (func) - func(validate_table(toml::parse(ss, source_path), source_path)); + func(validate_table(toml::parse(ss, source_path, collect_trivia), source_path)); else - validate_table(toml::parse(ss, source_path), source_path); + validate_table(toml::parse(ss, source_path, collect_trivia), source_path); } } catch (const parse_error& err) @@ -61,7 +62,7 @@ bool parsing_should_succeed(std::string_view test_file, { INFO("Parsing string directly"sv) - parse_result result = toml::parse(toml_str, source_path); + parse_result result = toml::parse(toml_str, source_path, collect_trivia); if (result) { if (func) @@ -81,7 +82,7 @@ bool parsing_should_succeed(std::string_view test_file, INFO("Parsing from a string stream"sv) std::stringstream ss; ss.write(toml_str.data(), static_cast(toml_str.length())); - parse_result result = toml::parse(ss, source_path); + parse_result result = toml::parse(ss, source_path, collect_trivia); if (result) { if (func) diff --git a/tests/tests.hpp b/tests/tests.hpp index de1b4298..adb84ced 100644 --- a/tests/tests.hpp +++ b/tests/tests.hpp @@ -125,7 +125,8 @@ bool parsing_should_succeed(std::string_view test_file, uint32_t test_line, std::string_view toml_str, pss_func&& func = {}, - std::string_view source_path = {}); + std::string_view source_path = {}, + bool collect_trivia = false); bool parsing_should_fail(std::string_view test_file, uint32_t test_line, diff --git a/toml.hpp b/toml.hpp index 0599bf5e..8d25dffe 100644 --- a/toml.hpp +++ b/toml.hpp @@ -1103,6 +1103,24 @@ TOML_ENABLE_WARNINGS; #define TOML_ENABLE_FLOAT16 0 #endif +#ifndef TOML_ENABLE_ORDERED_TABLES +#define TOML_ENABLE_ORDERED_TABLES 0 +#endif + +#ifndef TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA +#define TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA 0 +#endif + +#ifndef TOML_DISABLE_NOEXCEPT_NOEXCEPT +#define TOML_DISABLE_NOEXCEPT_NOEXCEPT 0 + #ifdef _MSC_VER + #if _MSC_VER <= 1943 // Up to Visual Studio 2022 Version 17.13.6 + #undef TOML_DISABLE_NOEXCEPT_NOEXCEPT + #define TOML_DISABLE_NOEXCEPT_NOEXCEPT 1 + #endif + #endif +#endif + #if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) // not supported by any version of GCC or Clang as of 26/11/2020 // not supported by any version of ICC on Linux as of 11/01/2021 @@ -1630,6 +1648,7 @@ TOML_NAMESPACE_START // abi namespace indentation = indent_sub_tables | indent_array_elements, relaxed_float_precision = (1ull << 11), terse_key_value_pairs = (1ull << 12), + preserve_source_trivia = (1ull << 13) }; TOML_MAKE_FLAGS(format_flags); @@ -3614,6 +3633,28 @@ TOML_PUSH_WARNINGS; #undef max #endif +//******** impl/trivia_piece.hpp ************************************************************************************* + +TOML_NAMESPACE_START +{ + enum class TOML_CLOSED_ENUM trivia_type : uint8_t + { + whitespace, + comment, + trailing_comma + }; + + struct trivia_piece + { + std::string text; + + trivia_type type; + }; +} +TOML_NAMESPACE_END; + +//******** impl/node.hpp ********************************************************************************************* + TOML_NAMESPACE_START { class TOML_ABSTRACT_INTERFACE TOML_EXPORTED_CLASS node @@ -3622,6 +3663,8 @@ TOML_NAMESPACE_START friend class TOML_PARSER_TYPENAME; source_region source_{}; + std::optional> leading_trivia_; + std::optional> trailing_trivia_; template TOML_NODISCARD @@ -3978,6 +4021,30 @@ TOML_NAMESPACE_START return source_; } + TOML_PURE_INLINE_GETTER + const optional>& leading_trivia() const noexcept + { + return leading_trivia_; + } + + TOML_PURE_INLINE_GETTER + const optional>& trailing_trivia() const noexcept + { + return trailing_trivia_; + } + + TOML_EXPORTED_MEMBER_FUNCTION + void set_leading_trivia(optional> leading_trivia) noexcept + { + leading_trivia_ = leading_trivia; + } + + TOML_EXPORTED_MEMBER_FUNCTION + void set_trailing_trivia(optional> trailing_trivia) noexcept + { + trailing_trivia_ = trailing_trivia; + } + private: template @@ -6249,6 +6316,7 @@ TOML_NAMESPACE_START using vector_iterator = typename vector_type::iterator; using const_vector_iterator = typename vector_type::const_iterator; vector_type elems_; + optional> inner_trailing_trivia_; TOML_NODISCARD_CTOR TOML_EXPORTED_MEMBER_FUNCTION @@ -6324,6 +6392,18 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION array& operator=(array&& rhs) noexcept; + TOML_CONST_INLINE_GETTER + const optional> inner_trailing_trivia() const noexcept + { + return inner_trailing_trivia_; + } + + TOML_EXPORTED_MEMBER_FUNCTION + void set_inner_trailing_trivia(optional> trivia) noexcept + { + inner_trailing_trivia_ = trivia; + } + TOML_CONST_INLINE_GETTER node_type type() const noexcept final { @@ -7191,6 +7271,8 @@ TOML_NAMESPACE_START private: std::string key_; source_region source_; + std::optional> leading_trivia_; + std::optional> trailing_trivia_; public: @@ -7198,53 +7280,93 @@ TOML_NAMESPACE_START key() noexcept = default; TOML_NODISCARD_CTOR - explicit key(std::string_view k, source_region&& src = {}) // + explicit key(std::string_view k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(std::string_view k, const source_region& src) // + explicit key(std::string_view k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(std::string&& k, source_region&& src = {}) noexcept // + explicit key(std::string&& k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) noexcept // : key_{ std::move(k) }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(std::string&& k, const source_region& src) noexcept // + explicit key(std::string&& k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) noexcept // : key_{ std::move(k) }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(const char* k, source_region&& src = {}) // + explicit key(const char* k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(const char* k, const source_region& src) // + explicit key(const char* k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ k }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} #if TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD_CTOR - explicit key(std::wstring_view k, source_region&& src = {}) // + explicit key(std::wstring_view k, + source_region&& src = {}, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ impl::narrow(k) }, - source_{ std::move(src) } + source_{ std::move(src) }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} TOML_NODISCARD_CTOR - explicit key(std::wstring_view k, const source_region& src) // + explicit key(std::wstring_view k, + const source_region& src, + optional> leading_trivia = optional>(), + optional> trailing_trivia = optional>()) // : key_{ impl::narrow(k) }, - source_{ src } + source_{ src }, + leading_trivia_(leading_trivia), + trailing_trivia_(trailing_trivia) {} #endif @@ -7285,6 +7407,16 @@ TOML_NAMESPACE_START return source_; } + optional> leading_trivia() const noexcept + { + return leading_trivia_; + } + + optional> trailing_trivia() const noexcept + { + return trailing_trivia_; + } + TOML_PURE_INLINE_GETTER friend bool operator==(const key& lhs, const key& rhs) noexcept { @@ -7425,6 +7557,12 @@ TOML_NAMESPACE_START } TOML_NAMESPACE_END; +template<> struct std::hash { + std::size_t operator()(toml::key const& k) const noexcept { + return std::hash{}(k.str()); + } +}; + #ifdef _MSC_VER #pragma pop_macro("min") #pragma pop_macro("max") @@ -7441,6 +7579,12 @@ TOML_DISABLE_WARNINGS; #include TOML_ENABLE_WARNINGS; +//******** impl/std_list.hpp ***************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + //******** impl/table.hpp ******************************************************************************************** TOML_PUSH_WARNINGS; @@ -7454,6 +7598,8 @@ TOML_PUSH_WARNINGS; #undef max #endif +#include + TOML_IMPL_NAMESPACE_START { template @@ -7473,9 +7619,16 @@ TOML_IMPL_NAMESPACE_START friend class table_iterator; using proxy_type = table_proxy_pair; + +#if TOML_ENABLE_ORDERED_TABLES + using mutable_map_iterator = std::list>::iterator; + using const_map_iterator = std::list>::const_iterator; +#else using mutable_map_iterator = std::map>::iterator; using const_map_iterator = std::map>::const_iterator; - using map_iterator = std::conditional_t; +#endif + + using map_iterator = std::conditional_t; mutable map_iterator iter_; alignas(proxy_type) mutable unsigned char proxy_[sizeof(proxy_type)]; @@ -7626,13 +7779,25 @@ TOML_NAMESPACE_START { private: - using map_type = std::map>; +#if TOML_ENABLE_ORDERED_TABLES + using map_pair = std::pair; + using entries_type = std::list>; + using map_type = std::unordered_map; + map_type map_; + entries_type entries_; + using map_iterator = typename entries_type::iterator; + using const_map_iterator = typename entries_type::const_iterator; +#else using map_pair = std::pair; + using map_type = std::map>; using map_iterator = typename map_type::iterator; using const_map_iterator = typename map_type::const_iterator; map_type map_; + optional last_inserted_; +#endif bool inline_ = false; + optional> inner_trailing_trivia_; TOML_NODISCARD_CTOR TOML_EXPORTED_MEMBER_FUNCTION @@ -7667,6 +7832,18 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION table& operator=(table&& rhs) noexcept; + TOML_CONST_INLINE_GETTER + const optional> inner_trailing_trivia() const noexcept + { + return inner_trailing_trivia_; + } + + TOML_EXPORTED_MEMBER_FUNCTION + void set_inner_trailing_trivia(optional> trivia) noexcept + { + inner_trailing_trivia_ = trivia; + } + TOML_CONST_INLINE_GETTER node_type type() const noexcept final { @@ -7982,40 +8159,79 @@ TOML_NAMESPACE_START using const_iterator = toml::const_table_iterator; + TOML_EXPORTED_MEMBER_FUNCTION + optional last_inserted() noexcept + { + if (size() > 0) + { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ --entries_.end() }; +#else + return last_inserted_; +#endif + } + else + return std::nullopt; + } + TOML_PURE_INLINE_GETTER iterator begin() noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ entries_.begin() }; +#else return iterator{ map_.begin() }; +#endif } TOML_PURE_INLINE_GETTER const_iterator begin() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cbegin() }; +#else return const_iterator{ map_.cbegin() }; +#endif } TOML_PURE_INLINE_GETTER const_iterator cbegin() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cbegin() }; +#else return const_iterator{ map_.cbegin() }; +#endif } TOML_PURE_INLINE_GETTER iterator end() noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return iterator{ entries_.end() }; +#else return iterator{ map_.end() }; +#endif } TOML_PURE_INLINE_GETTER const_iterator end() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cend() }; +#else return const_iterator{ map_.cend() }; +#endif } TOML_PURE_INLINE_GETTER const_iterator cend() const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ entries_.cend() }; +#else return const_iterator{ map_.cend() }; +#endif } private: @@ -8074,7 +8290,11 @@ TOML_NAMESPACE_START using kvp_type = impl::copy_cv>; +#if TOML_ENABLE_ORDERED_TABLES + for (kvp_type& kvp : tbl.entries_) +#else for (kvp_type& kvp : tbl.map_) +#endif { using node_ref = impl::copy_cvref; static_assert(std::is_reference_v); @@ -8214,11 +8434,14 @@ TOML_NAMESPACE_START private: - TOML_PURE_GETTER - TOML_EXPORTED_MEMBER_FUNCTION - map_iterator get_lower_bound(std::string_view) noexcept; +#if !TOML_ENABLE_ORDERED_TABLES + TOML_PURE_GETTER + TOML_EXPORTED_MEMBER_FUNCTION + map_iterator get_lower_bound(std::string_view) noexcept; +#endif public: +#if !TOML_ENABLE_ORDERED_TABLES TOML_PURE_GETTER iterator lower_bound(std::string_view key) noexcept @@ -8231,6 +8454,7 @@ TOML_NAMESPACE_START { return const_iterator{ const_cast(*this).get_lower_bound(key) }; } +#endif // !TOML_ENABLE_ORDERED_TABLES #if TOML_ENABLE_WINDOWS_COMPAT @@ -8388,6 +8612,28 @@ TOML_NAMESPACE_START "ValueType argument of table::emplace_hint() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); +#if TOML_ENABLE_ORDERED_TABLES + auto toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + if constexpr (moving_node_ptr) + entries_.push_back(std::pair{ toml_key, + std::move(static_cast(args)...) }); + else + { + entries_.push_back(std::pair{ toml_key, + new impl::wrap_node{ static_cast(args)... } }); + } + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return iterator{ entry_ipos }; + } + else + { + return iterator{ ipos->second }; + } +#else map_iterator ipos = insert_with_hint(hint, toml::key{ static_cast(key) }, nullptr); // if second is nullptr then we successully claimed the key and inserted the empty sentinel, @@ -8415,6 +8661,7 @@ TOML_NAMESPACE_START } } return iterator{ ipos }; +#endif } } @@ -8445,6 +8692,18 @@ TOML_NAMESPACE_START } else { +#if TOML_ENABLE_ORDERED_TABLES + auto toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + auto table_ipos = insert_with_hint(iterator{ ipos->second }, + toml::key{ static_cast(key) }, + impl::make_node(static_cast(val), flags)); + return { iterator{ table_ipos }, true }; + } + return { iterator { ipos->second }, false }; +#else const auto key_view = std::string_view{ key }; map_iterator ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -8455,6 +8714,7 @@ TOML_NAMESPACE_START return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; +#endif } } @@ -8501,6 +8761,22 @@ TOML_NAMESPACE_START } else { +#if TOML_ENABLE_ORDERED_TABLES + toml::key toml_key = toml::key{ static_cast(key) }; + map_type::iterator ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + entries_.push_back(std::pair{ toml_key, impl::make_node(static_cast(val), flags) }); + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return { iterator{ entry_ipos }, true }; + } + else + { + ipos->second->second = impl::make_node(static_cast(val), flags); + return { iterator{ ipos->second }, false }; + } +#else const auto key_view = std::string_view{ key }; map_iterator ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -8515,6 +8791,7 @@ TOML_NAMESPACE_START (*ipos).second = impl::make_node(static_cast(val), flags); return { iterator{ ipos }, false }; } +#endif } } @@ -8548,6 +8825,22 @@ TOML_NAMESPACE_START "ValueType argument of table::emplace() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); +#if TOML_ENABLE_ORDERED_TABLES + toml::key toml_key = toml::key{ static_cast(key) }; + auto ipos = map_.find(toml_key); + if (ipos == map_.end()) + { + entries_.push_back( + { + toml_key, + impl::node_ptr{ new impl::wrap_node{ static_cast(args)... } } + }); + auto entry_ipos = std::prev(entries_.end()); + map_.insert({ toml_key, entry_ipos }); + return { iterator{ entry_ipos }, true }; + } + return { iterator{ ipos->second }, false }; +#else const auto key_view = std::string_view{ key }; auto ipos = get_lower_bound(key_view); if (ipos == map_.end() || ipos->first != key_view) @@ -8559,6 +8852,7 @@ TOML_NAMESPACE_START return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; +#endif } } @@ -9532,29 +9826,29 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path = {}, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::string_view file_path); + parse_result TOML_CALLCONV parse_file(std::string_view file_path, bool collect_trivia = false); #if TOML_HAS_CHAR8 TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path = {}, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::u8string_view file_path); + parse_result TOML_CALLCONV parse_file(std::u8string_view file_path, bool collect_trivia = false); #endif // TOML_HAS_CHAR8 @@ -9562,15 +9856,15 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse_file(std::wstring_view file_path); + parse_result TOML_CALLCONV parse_file(std::wstring_view file_path, bool collect_trivia = false); #endif // TOML_ENABLE_WINDOWS_COMPAT @@ -9578,17 +9872,17 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path); + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path, bool collect_trivia = false); #endif // TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path = {}); + parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path = {}, bool collect_trivia = false); TOML_NODISCARD TOML_EXPORTED_FREE_FUNCTION - parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path); + parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path, bool collect_trivia = false); TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS @@ -9763,6 +10057,12 @@ TOML_IMPL_NAMESPACE_START return !!(config_.flags & format_flags::terse_key_value_pairs); } + TOML_PURE_INLINE_GETTER + bool preserve_source_trivia() const noexcept + { + return !!(config_.flags & format_flags::preserve_source_trivia); + } + TOML_EXPORTED_MEMBER_FUNCTION void attach(std::ostream& stream) noexcept; @@ -9873,6 +10173,17 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION void print(const toml::table&); + TOML_EXPORTED_MEMBER_FUNCTION + void print_trivia(const std::vector& trivia); + + using impl::formatter::print_value; + + TOML_EXPORTED_MEMBER_FUNCTION + void print_value(const node& node); + + TOML_EXPORTED_MEMBER_FUNCTION + void print_kvp(const key& k, const node& v); + TOML_EXPORTED_MEMBER_FUNCTION void print(); @@ -10744,11 +11055,15 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE node::node(node && other) noexcept // - : source_{ std::exchange(other.source_, {}) } + : source_{ std::exchange(other.source_, {}) }, + leading_trivia_(other.leading_trivia_), + trailing_trivia_(other.trailing_trivia_) {} TOML_EXTERNAL_LINKAGE - node::node(const node& /*other*/) noexcept + node::node(const node& other) noexcept + : leading_trivia_(other.leading_trivia_), + trailing_trivia_(other.trailing_trivia_) { // does not copy source information - this is not an error // @@ -10756,13 +11071,15 @@ TOML_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - node& node::operator=(const node& /*rhs*/) noexcept + node& node::operator=(const node& rhs) noexcept { // does not copy source information - this is not an error // // see https://github.com/marzer/tomlplusplus/issues/49#issuecomment-665089577 source_ = {}; + leading_trivia_ = rhs.leading_trivia_; + trailing_trivia_ = rhs.trailing_trivia_; return *this; } @@ -10771,6 +11088,8 @@ TOML_NAMESPACE_START { if (&rhs != this) source_ = std::exchange(rhs.source_, {}); + leading_trivia_ = rhs.leading_trivia_; + trailing_trivia_ = rhs.trailing_trivia_; return *this; } @@ -11721,7 +12040,8 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE array::array(const array& other) // - : node(other) + : node(other), + inner_trailing_trivia_(other.inner_trailing_trivia_) { elems_.reserve(other.elems_.size()); for (const auto& elem : other) @@ -11735,7 +12055,8 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE array::array(array && other) noexcept // : node(std::move(other)), - elems_(std::move(other.elems_)) + elems_(std::move(other.elems_)), + inner_trailing_trivia_(other.inner_trailing_trivia_) { #if TOML_LIFETIME_HOOKS TOML_ARRAY_CREATED; @@ -11752,6 +12073,7 @@ TOML_NAMESPACE_START elems_.reserve(rhs.elems_.size()); for (const auto& elem : rhs) elems_.emplace_back(impl::make_node(elem)); + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } @@ -11763,6 +12085,7 @@ TOML_NAMESPACE_START { node::operator=(std::move(rhs)); elems_ = std::move(rhs.elems_); + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } @@ -12094,17 +12417,36 @@ TOML_NAMESPACE_START if (!b->value) // empty node_views continue; +#if TOML_ENABLE_ORDERED_TABLES + entries_.push_back({ std::move(b->key), std::move(b->value) }); + map_.insert_or_assign(std::move(b->key), std::prev(entries_.end())); +#else map_.insert_or_assign(std::move(b->key), std::move(b->value)); +#endif } } TOML_EXTERNAL_LINKAGE table::table(const table& other) // : node(other), - inline_{ other.inline_ } + inline_{ other.inline_ }, +#if !TOML_ENABLE_ORDERED_TABLES + last_inserted_(other.last_inserted_), +#endif + inner_trailing_trivia_(other.inner_trailing_trivia_) { +#if TOML_ENABLE_ORDERED_TABLES + for (auto&& [k, v] : other.entries_) + { + entries_.push_back({ k, impl::make_node(*v) }); + map_.emplace(k, std::prev(entries_.end())); + } +#else for (auto&& [k, v] : other.map_) + { map_.emplace_hint(map_.end(), k, impl::make_node(*v)); + } +#endif #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -12115,7 +12457,13 @@ TOML_NAMESPACE_START table::table(table && other) noexcept // : node(std::move(other)), map_{ std::move(other.map_) }, - inline_{ other.inline_ } +#if TOML_ENABLE_ORDERED_TABLES + entries_{ std::move(other.entries_) }, +#else + last_inserted_(other.last_inserted_), +#endif + inline_{ other.inline_ }, + inner_trailing_trivia_(other.inner_trailing_trivia_) { #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -12129,9 +12477,22 @@ TOML_NAMESPACE_START { node::operator=(rhs); map_.clear(); +#if TOML_ENABLE_ORDERED_TABLES + entries_.clear(); + for (auto&& [k, v] : rhs.entries_) + { + entries_.push_back({ k, impl::make_node(*v) }); + map_.emplace(k, std::prev(entries_.end())); + } +#else for (auto&& [k, v] : rhs.map_) + { map_.emplace_hint(map_.end(), k, impl::make_node(*v)); + } + last_inserted_ = rhs.last_inserted_; +#endif inline_ = rhs.inline_; + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; } return *this; } @@ -12144,6 +12505,10 @@ TOML_NAMESPACE_START node::operator=(std::move(rhs)); map_ = std::move(rhs.map_); inline_ = rhs.inline_; + inner_trailing_trivia_ = rhs.inner_trailing_trivia_; +#if !TOML_ENABLE_ORDERED_TABLES + last_inserted_ = rhs.last_inserted_; +#endif } return *this; } @@ -12155,10 +12520,17 @@ TOML_NAMESPACE_START if (map_.empty()) return false; +#if TOML_ENABLE_ORDERED_TABLES + if (ntype == node_type::none) + ntype = entries_.cbegin()->second->type(); + + for (auto&& [k, v] : entries_) +#else if (ntype == node_type::none) ntype = map_.cbegin()->second->type(); for (auto&& [k, v] : map_) +#endif { TOML_UNUSED(k); if (v->type() != ntype) @@ -12177,9 +12549,19 @@ TOML_NAMESPACE_START first_nonmatch = {}; return false; } + if (ntype == node_type::none) +#if TOML_ENABLE_ORDERED_TABLES + ntype = entries_.cbegin()->second->type(); +#else ntype = map_.cbegin()->second->type(); +#endif + +#if TOML_ENABLE_ORDERED_TABLES + for (const auto& [k, v] : entries_) +#else for (const auto& [k, v] : map_) +#endif { TOML_UNUSED(k); if (v->type() != ntype) @@ -12205,8 +12587,12 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE node* table::get(std::string_view key) noexcept { - if (auto it = map_.find(key); it != map_.end()) + if (auto it = map_.find(toml::key{ key }); it != map_.end()) +#if TOML_ENABLE_ORDERED_TABLES + return it->second->second.get(); +#else return it->second.get(); +#endif return nullptr; } @@ -12234,48 +12620,86 @@ TOML_NAMESPACE_START return *n; } +#if !TOML_ENABLE_ORDERED_TABLES TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::map_iterator table::get_lower_bound(std::string_view key) noexcept { return map_.lower_bound(key); } +#endif // !TOML_ENABLE_ORDERED_TABLES TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::iterator table::find(std::string_view key) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + auto ipos = map_.find(toml::key{ key }); + if (ipos == map_.end()) + { + return iterator{ entries_.end() }; + } + return iterator{ ipos->second }; +#else return iterator{ map_.find(key) }; +#endif } TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE table::const_iterator table::find(std::string_view key) const noexcept { +#if TOML_ENABLE_ORDERED_TABLES + return const_iterator{ map_.find(toml::key{ key })->second }; +#else return const_iterator{ map_.find(key) }; +#endif } TOML_EXTERNAL_LINKAGE table::map_iterator table::erase(const_map_iterator pos) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + map_.erase(pos->first); + return entries_.erase(pos); +#else return map_.erase(pos); +#endif } TOML_EXTERNAL_LINKAGE table::map_iterator table::erase(const_map_iterator begin, const_map_iterator end) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + for (auto ipos = begin; ipos != end; ipos++) { + map_.erase(begin->first); + } + return entries_.erase(begin, end); +#else return map_.erase(begin, end); +#endif } TOML_EXTERNAL_LINKAGE size_t table::erase(std::string_view key) noexcept { +#if TOML_ENABLE_ORDERED_TABLES + size_t result = map_.erase(toml::key{ key }); + auto ipos = map_.find(toml::key{ key }); + if (ipos != map_.end()) + { + map_.erase(ipos); + entries_.erase(ipos->second); + } + return result; +#else if (auto it = map_.find(key); it != map_.end()) { map_.erase(it); return size_t{ 1 }; } return size_t{}; +#endif } TOML_EXTERNAL_LINKAGE @@ -12286,7 +12710,11 @@ TOML_NAMESPACE_START for (auto it = map_.begin(); it != map_.end();) { +#if TOML_ENABLE_ORDERED_TABLES + if (auto arr = it->second->second->as_array()) +#else if (auto arr = it->second->as_array()) +#endif { if (recursive) arr->prune(true); @@ -12297,7 +12725,11 @@ TOML_NAMESPACE_START continue; } } +#if TOML_ENABLE_ORDERED_TABLES + else if (auto tbl = it->second->second->as_table()) +#else else if (auto tbl = it->second->as_table()) +#endif { if (recursive) tbl->prune(true); @@ -12323,7 +12755,26 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE table::map_iterator table::insert_with_hint(const_iterator hint, key && k, impl::node_ptr && v) { - return map_.emplace_hint(const_map_iterator{ hint }, std::move(k), std::move(v)); +#if TOML_ENABLE_ORDERED_TABLES + auto ipos = map_.find(k); + if (ipos == map_.end()) + { + entries_.emplace_back(std::pair{ k, std::move(v) }); + auto entry_ipos = std::prev(entries_.end()); + map_.emplace(std::move(k), entry_ipos); + return entry_ipos; + } + else + { + return ipos->second; + } +#else + auto prev_size = map_.size(); + auto ipos = map_.emplace_hint(const_map_iterator{ hint }, std::move(k), std::move(v)); + if (map_.size() > prev_size) + last_inserted_ = toml::table_iterator { ipos }; + return ipos; +#endif } TOML_PURE_GETTER @@ -12335,7 +12786,11 @@ TOML_NAMESPACE_START if (lhs.map_.size() != rhs.map_.size()) return false; +#if TOML_ENABLE_ORDERED_TABLES + for (auto l = lhs.entries_.begin(), r = rhs.entries_.begin(), e = lhs.entries_.end(); l != e; l++, r++) +#else for (auto l = lhs.map_.begin(), r = rhs.map_.begin(), e = lhs.map_.end(); l != e; l++, r++) +#endif { if (l->first != r->first) return false; @@ -13308,6 +13763,8 @@ TOML_ANON_NAMESPACE_START std::vector> segments; std::vector starts; std::vector ends; + std::vector> leading_trivias; + std::vector> trailing_trivias; void clear() noexcept { @@ -13315,14 +13772,25 @@ TOML_ANON_NAMESPACE_START segments.clear(); starts.clear(); ends.clear(); + leading_trivias.clear(); + trailing_trivias.clear(); } - void push_back(std::string_view segment, source_position b, source_position e) + void push_back( + std::string_view segment, + source_position b, + source_position e, + std::optional> leading_trivia, + std::optional> trailing_trivia) { segments.push_back({ buffer.length(), segment.length() }); buffer.append(segment); starts.push_back(b); ends.push_back(e); + if (leading_trivia.has_value()) + leading_trivias.push_back(*leading_trivia); + if (trailing_trivia.has_value()) + trailing_trivias.push_back(*trailing_trivia); } TOML_PURE_INLINE_GETTER @@ -13522,6 +13990,8 @@ TOML_IMPL_NAMESPACE_START std::string string_buffer; std::string recording_buffer; // for diagnostics bool recording = false, recording_whitespace = true; + bool collect_trivia = false; + std::vector trivia_pieces; std::string_view current_scope; size_t nested_values = {}; #if !TOML_EXCEPTIONS @@ -13628,15 +14098,23 @@ TOML_IMPL_NAMESPACE_START { return_if_error_or_eof({}); + std::string piece; bool consumed = false; while (!is_eof() && is_horizontal_whitespace(*cp)) { if TOML_UNLIKELY(!is_ascii_horizontal_whitespace(*cp)) set_error_and_return_default("expected space or tab, saw '"sv, escaped_codepoint{ *cp }, "'"sv); + if (collect_trivia) + piece.append(cp->bytes, cp->count); + consumed = true; advance_and_return_if_error({}); } + + if (collect_trivia && !piece.empty()) + trivia_pieces.push_back(trivia_piece{piece, trivia_type::whitespace}); + return consumed; } @@ -13648,10 +14126,15 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default( R"(vertical tabs '\v' and form-feeds '\f' are not legal line breaks in TOML)"sv); + std::string piece; + if (*cp == U'\r') { advance_and_return_if_error({}); // skip \r + if (collect_trivia) + piece.append(cp->bytes, cp->count); + if TOML_UNLIKELY(is_eof()) set_error_and_return_default("expected '\\n' after '\\r', saw EOF"sv); @@ -13663,6 +14146,11 @@ TOML_IMPL_NAMESPACE_START else if (*cp != U'\n') return false; + if (collect_trivia) { + piece.append(cp->bytes, cp->count); + trivia_pieces.push_back(trivia_piece{piece, trivia_type::whitespace}); + } + advance_and_return_if_error({}); // skip \n return true; } @@ -13693,12 +14181,23 @@ TOML_IMPL_NAMESPACE_START push_parse_scope("comment"sv); + std::string piece; + + if (collect_trivia) + piece.append(cp->bytes, cp->count); + advance_and_return_if_error({}); // skip the '#' while (!is_eof()) { - if (consume_line_break()) + if (consume_line_break()) { + if (collect_trivia) + trivia_pieces.insert( + trivia_pieces.end() - 1, + trivia_piece{piece, trivia_type::comment} + ); return true; + } return_if_error({}); #if TOML_LANG_AT_LEAST(1, 0, 0) @@ -13714,9 +14213,15 @@ TOML_IMPL_NAMESPACE_START "unicode surrogates (U+D800 to U+DFFF) are explicitly prohibited in comments"sv); #endif + if (collect_trivia) + piece.append(cp->bytes, cp->count); + advance_and_return_if_error({}); } + if (collect_trivia) + trivia_pieces.push_back(trivia_piece{piece, trivia_type::comment}); + return true; } @@ -15010,6 +15515,13 @@ TOML_IMPL_NAMESPACE_START else if (*cp == U'_') set_error_and_return_default("values may not begin with underscores"sv); + std::vector leading_trivia; + if (collect_trivia) + { + leading_trivia = trivia_pieces; + trivia_pieces.clear(); + } + const auto begin_pos = cp->position; node_ptr val; @@ -15470,6 +15982,8 @@ TOML_IMPL_NAMESPACE_START } val->source_ = { begin_pos, current_position(1), reader.source_path() }; + if (collect_trivia) + val->leading_trivia_ = leading_trivia; return val; } @@ -15489,6 +16003,12 @@ TOML_IMPL_NAMESPACE_START std::string_view key_segment; const auto key_begin = current_position(); + std::vector leading_trivia; + if (collect_trivia) { + leading_trivia = trivia_pieces; + trivia_pieces.clear(); + } + // bare_key_segment if (is_bare_key_character(*cp)) key_segment = parse_bare_key_segment(); @@ -15527,7 +16047,16 @@ TOML_IMPL_NAMESPACE_START consume_leading_whitespace(); // store segment - key_buffer.push_back(key_segment, key_begin, key_end); + key_buffer.push_back( + key_segment, + key_begin, + key_end, + collect_trivia ? std::optional(leading_trivia) : std::optional>(), + collect_trivia ? std::optional(trivia_pieces) : std::optional>() + ); + + if (collect_trivia) + trivia_pieces.clear(); // eof or no more key to come if (is_eof() || *cp != U'.') @@ -15550,7 +16079,9 @@ TOML_IMPL_NAMESPACE_START return key{ key_buffer[segment_index], - source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path } + source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path }, + collect_trivia ? key_buffer.leading_trivias[segment_index] : optional>(), + collect_trivia ? key_buffer.trailing_trivias[segment_index] : optional>() }; } @@ -15567,6 +16098,9 @@ TOML_IMPL_NAMESPACE_START source_position header_end_pos; bool is_arr = false; + std::vector leading_trivia = trivia_pieces; + trivia_pieces.clear(); + // parse header { // skip first '[' @@ -15630,10 +16164,18 @@ TOML_IMPL_NAMESPACE_START for (size_t i = 0, e = key_buffer.size() - 1u; i < e; i++) { const std::string_view segment = key_buffer[i]; +#if TOML_ENABLE_ORDERED_TABLES + auto pit = parent->find(segment); +#else auto pit = parent->lower_bound(segment); +#endif // parent already existed +#if TOML_ENABLE_ORDERED_TABLES + if (pit != parent->end()) +#else if (pit != parent->end() && pit->first == segment) +#endif { node& p = pit->second; @@ -15684,13 +16226,21 @@ TOML_IMPL_NAMESPACE_START } const auto last_segment = key_buffer.back(); +#if TOML_ENABLE_ORDERED_TABLES + auto it = parent->find(last_segment); +#else auto it = parent->lower_bound(last_segment); +#endif // if there was already a matching node some sanity checking is necessary; // this is ok if we're making an array and the existing element is already an array (new element) // or if we're making a table and the existing element is an implicitly-created table (promote it), // otherwise this is a redefinition error. +#if TOML_ENABLE_ORDERED_TABLES + if (it != parent->end()) +#else if (it != parent->end() && it->first == last_segment) +#endif { node& matching_node = it->second; if (auto arr = matching_node.as_array(); @@ -15723,6 +16273,7 @@ TOML_IMPL_NAMESPACE_START implicit_tables.erase(implicit_tables.cbegin() + (found - implicit_tables.data())); tbl->source_.begin = header_begin_pos; tbl->source_.end = header_end_pos; + tbl->set_leading_trivia(optional>{leading_trivia}); return tbl; } } @@ -15766,6 +16317,7 @@ TOML_IMPL_NAMESPACE_START table& tbl = tbl_arr.emplace_back
(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + tbl.set_leading_trivia(optional>{leading_trivia}); return &tbl; } @@ -15775,6 +16327,7 @@ TOML_IMPL_NAMESPACE_START it = parent->emplace_hint
(it, std::move(last_key)); table& tbl = it->second.ref_cast
(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + tbl.set_leading_trivia(optional>{leading_trivia}); return &tbl; } } @@ -15820,10 +16373,18 @@ TOML_IMPL_NAMESPACE_START for (size_t i = 0; i < key_buffer.size() - 1u; i++) { const std::string_view segment = key_buffer[i]; +#if TOML_ENABLE_ORDERED_TABLES + auto pit = tbl->find(segment); +#else auto pit = tbl->lower_bound(segment); +#endif // parent already existed +#if TOML_ENABLE_ORDERED_TABLES + if (pit != tbl->end()) +#else if (pit != tbl->end() && pit->first == segment) +#endif { table* p = pit->second.as_table(); @@ -15857,8 +16418,13 @@ TOML_IMPL_NAMESPACE_START // ensure this isn't a redefinition const std::string_view last_segment = key_buffer.back(); +#if TOML_ENABLE_ORDERED_TABLES + auto it = tbl->find(last_segment); + if (it != tbl->end()) +#else auto it = tbl->lower_bound(last_segment); if (it != tbl->end() && it->first == last_segment) +#endif { set_error("cannot redefine existing "sv, to_sv(it->second.type()), @@ -15928,6 +16494,11 @@ TOML_IMPL_NAMESPACE_START root.source_.end = eof_pos; if (current_table && current_table != &root && current_table->source_.end <= current_table->source_.begin) current_table->source_.end = eof_pos; + if (collect_trivia) + { + current_table->trailing_trivia_ = trivia_pieces; + trivia_pieces.clear(); + } } static void update_region_ends(node& nde) noexcept @@ -15967,9 +16538,14 @@ TOML_IMPL_NAMESPACE_START } public: - parser(utf8_reader_interface&& reader_) // + parser(utf8_reader_interface&& reader_) + : parser(std::move(reader_), false) + {} + + parser(utf8_reader_interface&& reader_, bool collect_trivia) : reader{ reader_ } { + this->collect_trivia = collect_trivia; root.source_ = { prev_pos, prev_pos, reader.source_path() }; if (!reader.peek_eof()) @@ -16042,6 +16618,11 @@ TOML_IMPL_NAMESPACE_START if (prev == parse_type::val) { prev = parse_type::comma; + if (collect_trivia) + { + arr.back().set_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } advance_and_return_if_error_or_eof({}); continue; } @@ -16074,6 +16655,14 @@ TOML_IMPL_NAMESPACE_START } } + if (collect_trivia) + { + if (prev == parse_type::comma) + trivia_pieces.insert(trivia_pieces.begin(), trivia_piece{",", trivia_type::trailing_comma}); + arr.set_inner_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } + return_if_error({}); return arr_ptr; } @@ -16122,6 +16711,11 @@ TOML_IMPL_NAMESPACE_START if (prev == parse_type::kvp) { prev = parse_type::comma; + if (collect_trivia) + { + tbl.last_inserted().value()->second.set_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } advance_and_return_if_error_or_eof({}); } else @@ -16131,9 +16725,9 @@ TOML_IMPL_NAMESPACE_START // closing '}' else if (*cp == U'}') { - if constexpr (!TOML_LANG_UNRELEASED) // toml/issues/516 (newlines/trailing commas in inline tables) + if (prev == parse_type::comma) { - if (prev == parse_type::comma) + if constexpr (!TOML_LANG_UNRELEASED) // toml/issues/516 (newlines/trailing commas in inline tables) { set_error_and_return_default("expected key-value pair, saw closing '}' (dangling comma)"sv); continue; @@ -16159,6 +16753,14 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("expected key or closing '}', saw '"sv, to_sv(*cp), "'"sv); } + if (collect_trivia) + { + if (prev == parse_type::comma) + trivia_pieces.insert(trivia_pieces.begin(), trivia_piece{ ",", trivia_type::trailing_comma }); + tbl.set_inner_trailing_trivia(optional>{ trivia_pieces }); + trivia_pieces.clear(); + } + return_if_error({}); return tbl_ptr; } @@ -16194,14 +16796,14 @@ TOML_ANON_NAMESPACE_START { TOML_NODISCARD TOML_INTERNAL_LINKAGE - parse_result do_parse(utf8_reader_interface && reader) + parse_result do_parse(utf8_reader_interface && reader, bool collect_trivia) { - return impl::parser{ std::move(reader) }; + return impl::parser{ std::move(reader), collect_trivia }; } TOML_NODISCARD TOML_INTERNAL_LINKAGE - parse_result do_parse_file(std::string_view file_path) + parse_result do_parse_file(std::string_view file_path, bool collect_trivia) { #if TOML_EXCEPTIONS #define TOML_PARSE_FILE_ERROR(msg, path) \ @@ -16244,12 +16846,12 @@ TOML_ANON_NAMESPACE_START std::vector file_data; file_data.resize(static_cast(file_size)); file.read(file_data.data(), static_cast(file_size)); - return parse(std::string_view{ file_data.data(), file_data.size() }, std::move(file_path_str)); + return parse(std::string_view{ file_data.data(), file_data.size() }, std::move(file_path_str), collect_trivia); } // otherwise parse it using the streams else - return parse(file, std::move(file_path_str)); + return parse(file, std::move(file_path_str), collect_trivia); #undef TOML_PARSE_FILE_ERROR } @@ -16261,56 +16863,56 @@ TOML_NAMESPACE_START TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::string_view file_path) + parse_result TOML_CALLCONV parse_file(std::string_view file_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse_file(file_path); + return TOML_ANON_NAMESPACE::do_parse_file(file_path, collect_trivia); } #if TOML_HAS_CHAR8 TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, source_path }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::string && source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, std::move(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::u8string_view file_path) + parse_result TOML_CALLCONV parse_file(std::u8string_view file_path, bool collect_trivia) { std::string file_path_str; file_path_str.resize(file_path.length()); memcpy(file_path_str.data(), file_path.data(), file_path.length()); - return TOML_ANON_NAMESPACE::do_parse_file(file_path_str); + return TOML_ANON_NAMESPACE::do_parse_file(file_path_str, collect_trivia); } #endif // TOML_HAS_CHAR8 @@ -16318,21 +16920,21 @@ TOML_NAMESPACE_START #if TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::string_view doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::istream & doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse_file(std::wstring_view file_path) + parse_result TOML_CALLCONV parse_file(std::wstring_view file_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse_file(impl::narrow(file_path)); + return TOML_ANON_NAMESPACE::do_parse_file(impl::narrow(file_path), collect_trivia); } #endif // TOML_ENABLE_WINDOWS_COMPAT @@ -16340,9 +16942,9 @@ TOML_NAMESPACE_START #if TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT TOML_EXTERNAL_LINKAGE - parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path) + parse_result TOML_CALLCONV parse(std::u8string_view doc, std::wstring_view source_path, bool collect_trivia) { - return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }); + return TOML_ANON_NAMESPACE::do_parse(TOML_ANON_NAMESPACE::utf8_reader{ doc, impl::narrow(source_path) }, collect_trivia); } #endif // TOML_HAS_CHAR8 && TOML_ENABLE_WINDOWS_COMPAT @@ -17014,52 +17616,128 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE void toml_formatter::print(const key& k) { + if (preserve_source_trivia() && k.leading_trivia().has_value()) + print_trivia(*k.leading_trivia()); print_string(k.str(), false, true, false); + if (preserve_source_trivia() && k.trailing_trivia().has_value()) + print_trivia(*k.trailing_trivia()); + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_trivia(const std::vector& trivia) + { + for (auto & piece : trivia) { + print_unformatted(piece.text); + } + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_value(const node& node) + { + const auto type = node.type(); + TOML_ASSUME(type != node_type::none); + switch (type) + { + case node_type::table: print_inline(*reinterpret_cast(&node)); break; + case node_type::array: print(*reinterpret_cast(&node)); break; + default: + if (preserve_source_trivia() && node.leading_trivia().has_value()) + print_trivia(node.leading_trivia().value()); + this->impl::formatter::print_value(node, type); + if (preserve_source_trivia() && node.trailing_trivia().has_value()) + print_trivia(node.trailing_trivia().value()); + } } TOML_EXTERNAL_LINKAGE void toml_formatter::print_inline(const table& tbl) { + if (preserve_source_trivia() && tbl.leading_trivia().has_value()) + print_trivia(tbl.leading_trivia().value()); + if (tbl.empty()) { - print_unformatted("{}"sv); + print_unformatted("{"sv); + if (preserve_source_trivia() && tbl.inner_trailing_trivia().has_value()) + print_trivia(tbl.inner_trailing_trivia().value()); + print_unformatted("}"sv); + if (preserve_source_trivia() && tbl.trailing_trivia().has_value()) + print_trivia(tbl.trailing_trivia().value()); return; } - print_unformatted("{ "sv); + if (preserve_source_trivia()) + print_unformatted("{"sv); + else + print_unformatted("{ "sv); bool first = false; for (auto&& [k, v] : tbl) { if (first) - print_unformatted(", "sv); + { + if (preserve_source_trivia()) + print_unformatted(","sv); + else + print_unformatted(", "sv); + } first = true; - print(k); - if (terse_kvps()) - print_unformatted("="sv); - else - print_unformatted(" = "sv); + print_kvp(k, v); + } - const auto type = v.type(); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); - } + if (preserve_source_trivia()) + { + if (tbl.inner_trailing_trivia().has_value()) + print_trivia(tbl.inner_trailing_trivia().value()); + print_unformatted("}"sv); } + else + print_unformatted(" }"sv); - print_unformatted(" }"sv); + if (preserve_source_trivia() && tbl.trailing_trivia().has_value()) + print_trivia(tbl.trailing_trivia().value()); + } + + TOML_EXTERNAL_LINKAGE + void toml_formatter::print_kvp(const key& k, const node& v) + { + print(k); + if (terse_kvps()) + print_unformatted("="sv); + else if (!preserve_source_trivia()) + print_unformatted(" = "sv); + else + { + if (!k.trailing_trivia().has_value()) + print_unformatted(" "sv); + print_unformatted("="sv); + if (!v.leading_trivia().has_value()) + print_unformatted(" "sv); + } + + print_value(v); } TOML_EXTERNAL_LINKAGE void toml_formatter::print(const array& arr) { + if (preserve_source_trivia() && arr.leading_trivia().has_value()) + print_trivia(arr.leading_trivia().value()); + if (arr.empty()) { - print_unformatted("[]"sv); + if (preserve_source_trivia() && arr.inner_trailing_trivia().has_value()) + { + print_unformatted("["sv); + print_trivia(arr.inner_trailing_trivia().value()); + print_unformatted("]"sv); + } + else + print_unformatted("[]"sv); + + if (preserve_source_trivia() && arr.trailing_trivia().has_value()) + print_trivia(arr.trailing_trivia().value()); return; } @@ -17078,44 +17756,45 @@ TOML_NAMESPACE_START if (indent_array_elements()) increase_indent(); } - else - print_unformatted(' '); for (size_t i = 0; i < arr.size(); i++) { if (i > 0u) - { print_unformatted(','); - if (!multiline) + + auto& v = arr[i]; + if (!preserve_source_trivia() || !v.leading_trivia().has_value()) + { + if (multiline) + { + print_newline(true); + print_indent(); + } + else print_unformatted(' '); } + print_value(v); + } + + if (preserve_source_trivia() && arr.inner_trailing_trivia().has_value()) + print_trivia(arr.inner_trailing_trivia().value()); + else + { if (multiline) { + indent(original_indent); print_newline(true); print_indent(); } - - auto& v = arr[i]; - const auto type = v.type(); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); - } - } - if (multiline) - { - indent(original_indent); - print_newline(true); - print_indent(); + else + print_unformatted(' '); } - else - print_unformatted(' '); print_unformatted("]"sv); + + if (preserve_source_trivia() && arr.trailing_trivia().has_value()) + print_trivia(arr.trailing_trivia().value()); } TOML_EXTERNAL_LINKAGE @@ -17139,20 +17818,11 @@ TOML_NAMESPACE_START continue; pending_table_separator_ = true; - print_newline(); - print_indent(); - print(k); - if (terse_kvps()) - print_unformatted("="sv); - else - print_unformatted(" = "sv); - TOML_ASSUME(type != node_type::none); - switch (type) - { - case node_type::table: print_inline(*reinterpret_cast(&v)); break; - case node_type::array: print(*reinterpret_cast(&v)); break; - default: print_value(v, type); + if (!preserve_source_trivia() || !k.leading_trivia().has_value()) { + print_newline(); + print_indent(); } + print_kvp(k, v); } const auto print_key_path = [&]() @@ -17262,6 +17932,9 @@ TOML_NAMESPACE_START if (dump_failed_parse_result()) return; + if (preserve_source_trivia() && source().leading_trivia().has_value()) + print_trivia(source().leading_trivia().value()); + switch (auto source_type = source().type()) { case node_type::table: @@ -17281,6 +17954,9 @@ TOML_NAMESPACE_START default: print_value(source(), source_type); } + + if (preserve_source_trivia() && source().trailing_trivia().has_value()) + print_trivia(source().trailing_trivia().value()); } } TOML_NAMESPACE_END;