From 209e9b6faaac85938f17469c759ea47fdca3e866 Mon Sep 17 00:00:00 2001 From: Mark Gillard Date: Wed, 10 Nov 2021 11:17:15 +0200 Subject: [PATCH] added `toml::key` (closes #82) also: - added `table::lower_bound()` - added `table::emplace_hint()` - changed `table` key type to `toml::key` - clarified value initializer static assert messages - lots of misc refactoring --- CHANGELOG.md | 45 +- docs/poxy.toml | 19 +- include/toml++/impl/array.h | 20 +- include/toml++/impl/array.inl | 4 +- include/toml++/impl/date_time.h | 64 +- include/toml++/impl/forward_declarations.h | 3 + include/toml++/impl/json_formatter.inl | 2 +- include/toml++/impl/key.h | 303 ++++++ include/toml++/impl/make_node.h | 43 +- include/toml++/impl/node_view.h | 11 +- include/toml++/impl/parser.inl | 398 ++++---- include/toml++/impl/table.h | 239 +++-- include/toml++/impl/table.inl | 26 +- include/toml++/impl/toml_formatter.h | 7 +- include/toml++/impl/toml_formatter.inl | 34 +- include/toml++/impl/value.h | 2 +- include/toml++/impl/yaml_formatter.inl | 2 +- tests/parsing_tables.cpp | 42 + toml++.natvis | 27 +- toml++.vcxproj | 3 +- toml++.vcxproj.filters | 3 + toml.hpp | 1044 +++++++++++++------- 22 files changed, 1545 insertions(+), 796 deletions(-) create mode 100644 include/toml++/impl/key.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 117df81c..e0092187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ code changes at callsites or in build systems are indicated with ⚠️. #### Fixes: - ⚠️ fixed incorrect `noexcept` specifications on many functions -- fixed `json_formatter` not formatting inf and nan incorrectly -- fixed `table` init-list constructor requiring double-brackets +- fixed `toml::json_formatter` not formatting inf and nan incorrectly +- fixed `toml::table` init-list constructor requiring double-brackets - fixed `TOML_API` + extern templates causing linker errors in some circumstances - fixed an illegal table redefinition edge case (#112) (@python36) - fixed documentation issues @@ -34,32 +34,37 @@ code changes at callsites or in build systems are indicated with ⚠️. - fixed parser not correctly round-tripping the format of binary and octal integers in some cases #### Additions: -- added `array::at()` and `table::at()` -- added `array::replace()` (#109) (@LebJe) -- added `array::resize()` param `default_init_flags` -- added `format_flags::allow_binary_integers` -- added `format_flags::allow_hexadecimal_integers` -- added `format_flags::allow_octal_integers` -- added `format_flags::allow_real_tabs_in_strings` -- added `format_flags::indent_array_elements` -- added `format_flags::indent_sub_tables` -- added `format_flags::quote_infinities_and_nans` -- added `operator->` to `value` for class types +- added `operator->` to `toml::value` for class types - added `parse_benchmark` example +- added `toml::array::at()` (same semantics as `std::vector::at()`) +- added `toml::array::replace()` (#109) (@LebJe) +- added `toml::array::resize()` param `default_init_flags` +- added `toml::format_flags::allow_binary_integers` +- added `toml::format_flags::allow_hexadecimal_integers` +- added `toml::format_flags::allow_octal_integers` +- added `toml::format_flags::allow_real_tabs_in_strings` +- added `toml::format_flags::indent_array_elements` +- added `toml::format_flags::indent_sub_tables` +- added `toml::format_flags::quote_infinities_and_nans` +- added `toml::key` - provides a facility to access the source_regions of parsed keys (#82) (@vaartis) +- added `toml::table::at()` (same semantics as `std::map::at()`) +- added `toml::table::emplace_hint()` (same semantics as `std::map::emplace_hint()`) +- added `toml::table::lower_bound()` (same semantics as `std::map::lower_bound()`) +- added `toml::yaml_formatter` - added `TOML_ENABLE_FORMATTERS` option -- added `yaml_formatter` - added clang's enum annotation attributes to all enums - added formatter indentation flags (#120) (@W4RH4WK) -- added magic `value_flags` constant `preserve_source_value_flags` +- added magic `toml::value_flags` constant `toml::preserve_source_value_flags` - added support for Unicode 14.0 - added value flags to array + table insert methods (#44) (@levicki) #### Changes: -- ⚠️ `format_flags` is now backed by `uint64_t` (was previously `uint8_t`) -- ⚠️ `source_index` is now an alias for `uint32_t` unconditionally (was previously dependent on `TOML_LARGE_FILES`) -- ⚠️ `value_flags` is now backed by `uint16_t` (was previously `uint8_t`) +- ⚠️ `toml::format_flags` is now backed by `uint64_t` (was previously `uint8_t`) +- ⚠️ `toml::source_index` is now an alias for `uint32_t` unconditionally (was previously dependent on `TOML_LARGE_FILES`) +- ⚠️ `toml::table` now uses `toml::key` as the key type (was previously `std::string`) +- ⚠️ `toml::value_flags` is now backed by `uint16_t` (was previously `uint8_t`) - ⚠️ made all overloaded operators 'hidden friends' where possible -- ⚠️ renamed `default_formatter` to `toml_formatter` (`default_formatter` is now an alias) +- ⚠️ renamed `toml::default_formatter` to `toml::toml_formatter` (`toml::default_formatter` is now an alias) - ⚠️ renamed `TOML_PARSER` option to `TOML_ENABLE_PARSER` (`TOML_PARSER` will continue to work but is deprecated) - ⚠️ renamed `TOML_UNRELEASED_FEATURES` to `TOML_ENABLE_UNRELEASED_FEATURES` (`TOML_UNRELEASED_FEATURES` will continue to work but is deprecated) - ⚠️ renamed `TOML_WINDOWS_COMPAT` to `TOML_ENABLE_WINDOWS_COMPAT` (`TOML_WINDOWS_COMPAT` will continue to work but is deprecated) @@ -71,7 +76,7 @@ code changes at callsites or in build systems are indicated with ⚠️. - updated conformance tests #### Removals: -- ⚠️ removed `format_flags::allow_value_format_flags` +- ⚠️ removed `toml::format_flags::allow_value_format_flags` - ⚠️ removed `TOML_LARGE_FILES` (it is now default - explicitly setting `TOML_LARGE_FILES` to `0` will invoke an `#error`) - removed unnecessary template machinery (esp. where ostreams were involved) - removed unnecessary uses of `final` diff --git a/docs/poxy.toml b/docs/poxy.toml index 3f3f7d14..fa75d832 100644 --- a/docs/poxy.toml +++ b/docs/poxy.toml @@ -80,17 +80,18 @@ string_literals = [ '_toml' ] [autolinks] -'(?:toml::)?parse[_ ]results?' = 'classtoml_1_1parse__result.html' -'(?:toml::)?parse[_ ]errors?' = 'classtoml_1_1parse__error.html' -'(?:toml::)?node[_ ]views?' = 'classtoml_1_1node__view.html' -'(?:toml::)?json[_ ]formatters?' = 'classtoml_1_1json__formatter.html' +'(?:toml::)?date[_-]times?' = 'structtoml_1_1date__time.html' '(?:toml::)?default[_ ]formatters?' = 'classtoml_1_1default__formatter.html' +'(?:toml::)?json[_ ]formatters?' = 'classtoml_1_1json__formatter.html' +'(?:toml::)?node[_ ]views?' = 'classtoml_1_1node__view.html' +'(?:toml::)?parse[_ ]errors?' = 'classtoml_1_1parse__error.html' +'(?:toml::)?parse[_ ]results?' = 'classtoml_1_1parse__result.html' '(?:toml::)?source[_ ]positions?' = 'structtoml_1_1source__position.html' '(?:toml::)?source[_ ]regions?' = 'structtoml_1_1source__region.html' -'toml::values?' = 'classtoml_1_1value.html' +'(?:toml::)?time[_ ]offsets?' = 'structtoml_1_1time__offset.html' +'(?:toml::)?toml[_ ]formatters?' = 'classtoml_1_1toml__formatter.html' +'(?:toml::)?yaml[_ ]formatters?' = 'classtoml_1_1yaml__formatter.html' 'toml::dates?' = 'structtoml_1_1date.html' +'toml::keys?' = 'classtoml_1_1key.html' 'toml::times?' = 'structtoml_1_1time.html' -'(?:toml::)?time[_ ]offsets?' = 'structtoml_1_1time__offset.html' -'(?:toml::)?date_times?' = 'structtoml_1_1date__time.html' -'(?:toml::)?toml_formatters?' = 'classtoml_1_1toml__formatter.html' -'(?:toml::)?json_formatters?' = 'classtoml_1_1json__formatter.html' +'toml::values?' = 'classtoml_1_1value.html' diff --git a/include/toml++/impl/array.h b/include/toml++/impl/array.h index b99c1af3..5b2fbe7f 100644 --- a/include/toml++/impl/array.h +++ b/include/toml++/impl/array.h @@ -22,8 +22,8 @@ TOML_IMPL_NAMESPACE_START friend class array_iterator; friend class TOML_NAMESPACE::array; - using raw_mutable_iterator = std::vector>::iterator; - using raw_const_iterator = std::vector>::const_iterator; + using raw_mutable_iterator = std::vector::iterator; + using raw_const_iterator = std::vector::const_iterator; using raw_iterator = std::conditional_t; mutable raw_iterator raw_; @@ -250,7 +250,7 @@ TOML_NAMESPACE_START /// \cond friend class TOML_PARSER_TYPENAME; - std::vector> elems_; + std::vector elems_; TOML_API void preinsertion_resize(size_t idx, size_t count); @@ -949,10 +949,10 @@ TOML_NAMESPACE_START preinsertion_resize(start_idx, count); size_t i = start_idx; for (size_t e = start_idx + count - 1u; i < e; i++) - elems_[i].reset(impl::make_node(val, flags)); + elems_[i] = impl::make_node(val, flags); //# potentially move the initial value into the last element - elems_[i].reset(impl::make_node(static_cast(val), flags)); + elems_[i] = impl::make_node(static_cast(val), flags); return { elems_.begin() + static_cast(start_idx) }; } } @@ -1001,9 +1001,9 @@ TOML_NAMESPACE_START continue; } if constexpr (std::is_rvalue_reference_v) - elems_[i++].reset(impl::make_node(std::move(*it), flags)); + elems_[i++] = impl::make_node(std::move(*it), flags); else - elems_[i++].reset(impl::make_node(*it, flags)); + elems_[i++] = impl::make_node(*it, flags); } return { elems_.begin() + static_cast(start_idx) }; } @@ -1104,7 +1104,7 @@ TOML_NAMESPACE_START } const auto it = elems_.begin() + (pos.raw_ - elems_.cbegin()); - it->reset(impl::make_node(static_cast(val), flags)); + *it = impl::make_node(static_cast(val), flags); return iterator{ it }; } @@ -1342,8 +1342,8 @@ TOML_NAMESPACE_START static bool equal_to_container(const array& lhs, const T& rhs) noexcept { using element_type = std::remove_const_t; - static_assert(impl::is_native || impl::is_losslessly_convertible_to_native, - "Container element type must be (or be promotable to) one of the TOML value types"); + static_assert(impl::is_losslessly_convertible_to_native, + "Container element type must be losslessly convertible one of the native TOML value types"); if (lhs.size() != rhs.size()) return false; diff --git a/include/toml++/impl/array.inl b/include/toml++/impl/array.inl index 693f90b2..228cd194 100644 --- a/include/toml++/impl/array.inl +++ b/include/toml++/impl/array.inl @@ -234,8 +234,8 @@ TOML_NAMESPACE_START continue; } - std::unique_ptr arr_storage = std::move(elems_[i]); - const auto leaf_count = arr->total_leaf_count(); + impl::node_ptr arr_storage = std::move(elems_[i]); + const auto leaf_count = arr->total_leaf_count(); if (leaf_count > 1u) preinsertion_resize(i + 1u, leaf_count - 1u); flatten_child(std::move(*arr), i); // increments i diff --git a/include/toml++/impl/date_time.h b/include/toml++/impl/date_time.h index 49347a38..dd5a6e2a 100644 --- a/include/toml++/impl/date_time.h +++ b/include/toml++/impl/date_time.h @@ -36,52 +36,55 @@ TOML_NAMESPACE_START {} /// \brief Equality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const date& lhs, const date& rhs) noexcept { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day; } /// \brief Inequality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const date& lhs, const date& rhs) noexcept { return lhs.year != rhs.year || lhs.month != rhs.month || lhs.day != rhs.day; } private: - TOML_NODISCARD - TOML_ALWAYS_INLINE + /// \cond + + TOML_PURE_GETTER static constexpr uint32_t pack(const date& d) noexcept { return (static_cast(d.year) << 16) | (static_cast(d.month) << 8) | static_cast(d.day); } + /// \endcond + public: /// \brief Less-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const date& lhs, const date& rhs) noexcept { return pack(lhs) < pack(rhs); } /// \brief Less-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const date& lhs, const date& rhs) noexcept { return pack(lhs) <= pack(rhs); } /// \brief Greater-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const date& lhs, const date& rhs) noexcept { return pack(lhs) > pack(rhs); } /// \brief Greater-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const date& lhs, const date& rhs) noexcept { return pack(lhs) >= pack(rhs); @@ -137,7 +140,7 @@ TOML_NAMESPACE_START {} /// \brief Equality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const time& lhs, const time& rhs) noexcept { return lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second @@ -145,45 +148,48 @@ TOML_NAMESPACE_START } /// \brief Inequality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const time& lhs, const time& rhs) noexcept { return !(lhs == rhs); } private: - TOML_NODISCARD - TOML_ALWAYS_INLINE + /// \cond + + TOML_PURE_GETTER static constexpr uint64_t pack(const time& t) noexcept { return static_cast(t.hour) << 48 | static_cast(t.minute) << 40 | static_cast(t.second) << 32 | static_cast(t.nanosecond); } + /// \endcond + public: /// \brief Less-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const time& lhs, const time& rhs) noexcept { return pack(lhs) < pack(rhs); } /// \brief Less-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const time& lhs, const time& rhs) noexcept { return pack(lhs) <= pack(rhs); } /// \brief Greater-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const time& lhs, const time& rhs) noexcept { return pack(lhs) > pack(rhs); } /// \brief Greater-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const time& lhs, const time& rhs) noexcept { return pack(lhs) >= pack(rhs); @@ -247,42 +253,42 @@ TOML_NAMESPACE_START {} /// \brief Equality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes == rhs.minutes; } /// \brief Inequality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes != rhs.minutes; } /// \brief Less-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes < rhs.minutes; } /// \brief Less-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes <= rhs.minutes; } /// \brief Greater-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes > rhs.minutes; } /// \brief Greater-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes >= rhs.minutes; @@ -355,28 +361,28 @@ TOML_NAMESPACE_START {} /// \brief Returns true if this date_time does not contain timezone offset information. - TOML_NODISCARD + TOML_PURE_GETTER constexpr bool is_local() const noexcept { return !offset.has_value(); } /// \brief Equality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const date_time& lhs, const date_time& rhs) noexcept { return lhs.date == rhs.date && lhs.time == rhs.time && lhs.offset == rhs.offset; } /// \brief Inequality operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs == rhs); } /// \brief Less-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const date_time& lhs, const date_time& rhs) noexcept { if (lhs.date != rhs.date) @@ -387,7 +393,7 @@ TOML_NAMESPACE_START } /// \brief Less-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const date_time& lhs, const date_time& rhs) noexcept { if (lhs.date != rhs.date) @@ -398,14 +404,14 @@ TOML_NAMESPACE_START } /// \brief Greater-than operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs <= rhs); } /// \brief Greater-than-or-equal-to operator. - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs < rhs); diff --git a/include/toml++/impl/forward_declarations.h b/include/toml++/impl/forward_declarations.h index dfb41283..ab92994d 100644 --- a/include/toml++/impl/forward_declarations.h +++ b/include/toml++/impl/forward_declarations.h @@ -89,6 +89,7 @@ TOML_NAMESPACE_START template class node_view; + class key; class array; class table; template @@ -111,6 +112,8 @@ TOML_NAMESPACE_END; TOML_IMPL_NAMESPACE_START { + using node_ptr = std::unique_ptr; + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); class parser; TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS diff --git a/include/toml++/impl/json_formatter.inl b/include/toml++/impl/json_formatter.inl index f002b342..4bb31459 100644 --- a/include/toml++/impl/json_formatter.inl +++ b/include/toml++/impl/json_formatter.inl @@ -42,7 +42,7 @@ TOML_NAMESPACE_START print_newline(true); print_indent(); - print_string(k, false); + print_string(k.str(), false); print_unformatted(" : "sv); const auto type = v.type(); diff --git a/include/toml++/impl/key.h b/include/toml++/impl/key.h new file mode 100644 index 00000000..1c16082f --- /dev/null +++ b/include/toml++/impl/key.h @@ -0,0 +1,303 @@ +//# 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 "source_region.h" +#include "std_utility.h" +#include "print_to_stream.h" +#include "header_start.h" + +TOML_NAMESPACE_START +{ + /// \brief A key parsed from a TOML document. + /// + /// \detail These are used as the internal keys for a toml::table: \cpp + /// const toml::table tbl = R"( + /// a = 1 + /// b = 2 + /// c = 3 + /// )"_toml; + /// + /// for (auto&& [k, v] : tbl) + /// std::cout << "key '"sv << k << "' defined at "sv << k.source() << "\n"; + /// \ecpp + /// \out + /// key 'a' defined at line 2, column 5 + /// key 'b' defined at line 3, column 7 + /// key 'c' defined at line 4, column 9 + /// \eout + class key + { + private: + std::string key_; + source_region source_; + + public: + /// \brief Default constructor. + key() noexcept = default; + + /// \brief Constructs a key from a string view and source region. + explicit key(std::string_view k, source_region&& src = {}) // + : key_{ k }, + source_{ std::move(src) } + {} + + /// \brief Constructs a key from a string view and source region. + explicit key(std::string_view k, const source_region& src) // + : key_{ k }, + source_{ src } + {} + + /// \brief Constructs a key from a string and source region. + explicit key(std::string&& k, source_region&& src = {}) noexcept // + : key_{ std::move(k) }, + source_{ std::move(src) } + {} + + /// \brief Constructs a key from a string and source region. + explicit key(std::string&& k, const source_region& src) noexcept // + : key_{ std::move(k) }, + source_{ src } + {} + + /// \brief Constructs a key from a c-string and source region. + explicit key(const char* k, source_region&& src = {}) // + : key_{ k }, + source_{ std::move(src) } + {} + + /// \brief Constructs a key from a c-string view and source region. + explicit key(const char* k, const source_region& src) // + : key_{ k }, + source_{ src } + {} + +#if TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Constructs a key from a wide string view and source region. + /// + /// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. + explicit key(std::wstring_view k, source_region&& src = {}) // + : key_{ impl::narrow(k) }, + source_{ std::move(src) } + {} + + /// \brief Constructs a key from a wide string and source region. + /// + /// \availability This constructor is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. + explicit key(std::wstring_view k, const source_region& src) // + : key_{ impl::narrow(k) }, + source_{ src } + {} + +#endif + + /// \name String operations + /// @{ + + /// \brief Returns a view of the key's underlying string. + TOML_PURE_INLINE_GETTER + std::string_view str() const noexcept + { + return std::string_view{ key_ }; + } + + /// \brief Returns a view of the key's underlying string. + TOML_PURE_INLINE_GETTER + /*implicit*/ operator std::string_view() const noexcept + { + return str(); + } + + /// \brief Returns true if the key's underlying string is empty. + TOML_PURE_INLINE_GETTER + bool empty() const noexcept + { + return key_.empty(); + } + + /// \brief Returns a pointer to the start of the key's underlying string. + TOML_PURE_INLINE_GETTER + const char* data() const noexcept + { + return key_.data(); + } + + /// \brief Returns the length of the key's underlying string. + TOML_PURE_INLINE_GETTER + size_t length() const noexcept + { + return key_.length(); + } + + /// @} + + /// \name Metadata + /// @{ + + /// \brief Returns the source region responsible for specifying this key during parsing. + TOML_PURE_INLINE_GETTER + const source_region& source() const noexcept + { + return source_; + } + + /// @} + + /// \name Equality and Comparison + /// \attention These operations only compare the underlying strings; source regions are ignored for the purposes of all comparison! + /// @{ + + /// \brief Returns true if `lhs.str() == rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator==(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ == rhs.key_; + } + + /// \brief Returns true if `lhs.str() != rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator!=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ != rhs.key_; + } + + /// \brief Returns true if `lhs.str() < rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator<(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ < rhs.key_; + } + + /// \brief Returns true if `lhs.str() <= rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator<=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ <= rhs.key_; + } + + /// \brief Returns true if `lhs.str() > rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator>(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ > rhs.key_; + } + + /// \brief Returns true if `lhs.str() >= rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator>=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ >= rhs.key_; + } + + /// \brief Returns true if `lhs.str() == rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator==(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ == rhs; + } + + /// \brief Returns true if `lhs.str() != rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator!=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ != rhs; + } + + /// \brief Returns true if `lhs.str() < rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator<(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ < rhs; + } + + /// \brief Returns true if `lhs.str() <= rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator<=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ <= rhs; + } + + /// \brief Returns true if `lhs.str() > rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator>(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ > rhs; + } + + /// \brief Returns true if `lhs.str() >= rhs`. + TOML_PURE_INLINE_GETTER + friend bool operator>=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ >= rhs; + } + + /// \brief Returns true if `lhs == rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator==(std::string_view lhs, const key& rhs) noexcept + { + return lhs == rhs.key_; + } + + /// \brief Returns true if `lhs != rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator!=(std::string_view lhs, const key& rhs) noexcept + { + return lhs != rhs.key_; + } + + /// \brief Returns true if `lhs < rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator<(std::string_view lhs, const key& rhs) noexcept + { + return lhs < rhs.key_; + } + + /// \brief Returns true if `lhs <= rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator<=(std::string_view lhs, const key& rhs) noexcept + { + return lhs <= rhs.key_; + } + + /// \brief Returns true if `lhs > rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator>(std::string_view lhs, const key& rhs) noexcept + { + return lhs > rhs.key_; + } + + /// \brief Returns true if `lhs >= rhs.str()`. + TOML_PURE_INLINE_GETTER + friend bool operator>=(std::string_view lhs, const key& rhs) noexcept + { + return lhs >= rhs.key_; + } + + /// @} + + /// \brief Prints the key's underlying string out to the stream. + friend std::ostream& operator<<(std::ostream& lhs, const key& rhs) + { + impl::print_to_stream(lhs, rhs.key_); + return lhs; + } + }; +} +TOML_NAMESPACE_END; + +/// \cond +TOML_IMPL_NAMESPACE_START +{ + template + inline constexpr bool is_key = std::is_same_v, toml::key>; + + template + inline constexpr bool is_key_or_convertible = is_key || std::is_constructible_v; +} +TOML_IMPL_NAMESPACE_END; +/// \endcond + +#include "header_end.h" diff --git a/include/toml++/impl/make_node.h b/include/toml++/impl/make_node.h index 95fee7d5..003c3d40 100644 --- a/include/toml++/impl/make_node.h +++ b/include/toml++/impl/make_node.h @@ -13,7 +13,7 @@ TOML_IMPL_NAMESPACE_START template TOML_NODISCARD TOML_ATTR(returns_nonnull) - auto* make_node_specialized(T && val, [[maybe_unused]] value_flags flags) + auto* make_node_impl_specialized(T && val, [[maybe_unused]] value_flags flags) { using unwrapped_type = unwrap_node>; static_assert(!std::is_same_v); @@ -45,8 +45,20 @@ TOML_IMPL_NAMESPACE_START static_assert(!is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, "Instantiating values from wide-character strings is only " "supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled."); - static_assert(is_native || is_losslessly_convertible_to_native, - "Value initializers must be (or be promotable to) one of the TOML value types"); + + if constexpr (!is_losslessly_convertible_to_native) + { + if constexpr (std::is_same_v) + static_assert(dependent_false, + "Integral value initializers must be losslessly convertible to int64_t"); + else if constexpr (std::is_same_v) + static_assert(dependent_false, + "Floating-point value initializers must be losslessly convertible to double"); + else + static_assert( + dependent_false, + "Value initializers must be losslessly convertible to one of the TOML value types"); + } if constexpr (is_wide_string) { @@ -69,12 +81,12 @@ TOML_IMPL_NAMESPACE_START template TOML_NODISCARD - auto* make_node(T && val, value_flags flags = preserve_source_value_flags) + auto* make_node_impl(T && val, value_flags flags = preserve_source_value_flags) { - using type = unwrap_node>; - if constexpr (std::is_same_v || is_node_view) + using unwrapped_type = unwrap_node>; + if constexpr (std::is_same_v || is_node_view) { - if constexpr (is_node_view) + if constexpr (is_node_view) { if (!val) return static_cast(nullptr); @@ -83,24 +95,24 @@ TOML_IMPL_NAMESPACE_START return static_cast(val).visit( [flags](auto&& concrete) { return static_cast( - make_node_specialized(static_cast(concrete), flags)); + make_node_impl_specialized(static_cast(concrete), flags)); }); } else - return make_node_specialized(static_cast(val), flags); + return make_node_impl_specialized(static_cast(val), flags); } template TOML_NODISCARD - auto* make_node(inserter && val, value_flags flags = preserve_source_value_flags) + auto* make_node_impl(inserter && val, value_flags flags = preserve_source_value_flags) { - return make_node(static_cast(val.value), flags); + return make_node_impl(static_cast(val.value), flags); } template || is_node_view || is_value || can_partially_represent_native)> struct inserted_type_of_ { - using type = std::remove_pointer_t()))>; + using type = std::remove_pointer_t()))>; }; template @@ -114,6 +126,13 @@ TOML_IMPL_NAMESPACE_START { using type = void; }; + + template + TOML_NODISCARD + node_ptr make_node(T && val, value_flags flags = preserve_source_value_flags) + { + return node_ptr{ make_node_impl(static_cast(val), flags) }; + } } TOML_IMPL_NAMESPACE_END; /// \endcond diff --git a/include/toml++/impl/node_view.h b/include/toml++/impl/node_view.h index 68f7714e..60d33c0b 100644 --- a/include/toml++/impl/node_view.h +++ b/include/toml++/impl/node_view.h @@ -614,7 +614,7 @@ TOML_NAMESPACE_START TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const toml::value&, template ); /// \brief Returns true if the viewed node is a value with the same value as RHS. - TOML_CONSTRAINED_TEMPLATE((impl::is_native || impl::is_losslessly_convertible_to_native), typename T) + TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native, typename T) TOML_NODISCARD friend bool operator==(const node_view& lhs, const T& rhs) noexcept(!impl::is_wide_string) { @@ -636,11 +636,10 @@ TOML_NAMESPACE_START return val && *val == rhs; } } - TOML_ASYMMETRICAL_EQUALITY_OPS( - const node_view&, - const T&, - TOML_CONSTRAINED_TEMPLATE((impl::is_native || impl::is_losslessly_convertible_to_native), - typename T)); + TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, + const T&, + TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native, + typename T)); /// \brief Returns true if the viewed node is an array with the same contents as the RHS initializer list. template diff --git a/include/toml++/impl/parser.inl b/include/toml++/impl/parser.inl index c4c9225c..1c5a2f22 100644 --- a/include/toml++/impl/parser.inl +++ b/include/toml++/impl/parser.inl @@ -875,101 +875,49 @@ TOML_ANON_NAMESPACE_START #define push_parse_scope_1(scope, line) push_parse_scope_2(scope, line) #define push_parse_scope(scope) push_parse_scope_1(scope, __LINE__) - // Q: "why not std::unique_ptr?? - // A: It caused a lot of bloat on some implementations so this exists an internal substitute. - class node_ptr - { - private: - node* node_ = {}; - - public: - TOML_NODISCARD_CTOR - node_ptr() noexcept = default; - - TOML_NODISCARD_CTOR - explicit node_ptr(node* n) noexcept // - : node_{ n } - {} - - ~node_ptr() noexcept - { - delete node_; - } - - node_ptr& operator=(node* val) noexcept - { - if (val != node_) - { - delete node_; - node_ = val; - } - return *this; - } - - node_ptr(const node_ptr&) = delete; - node_ptr& operator=(const node_ptr&) = delete; - node_ptr(node_ptr&&) = delete; - node_ptr& operator=(node_ptr&&) = delete; - - TOML_PURE_INLINE_GETTER - operator bool() const noexcept - { - return node_ != nullptr; - } - - TOML_PURE_INLINE_GETTER - node* operator->() noexcept - { - return node_; - } - - TOML_NODISCARD - node* release() noexcept - { - auto n = node_; - node_ = nullptr; - return n; - } - }; - struct parsed_key_buffer { std::string buffer; std::vector> segments; - source_region source; + std::vector starts; + std::vector ends; - void reset(source_position pos) noexcept + void clear() noexcept { buffer.clear(); segments.clear(); - source.begin = pos; + starts.clear(); + ends.clear(); } - void push_back(std::string_view segment) + void push_back(std::string_view segment, source_position b, source_position e) { segments.push_back({ buffer.length(), segment.length() }); buffer.append(segment); + starts.push_back(b); + ends.push_back(e); } - void finish(source_position pos) noexcept + TOML_PURE_INLINE_GETTER + std::string_view operator[](size_t i) const noexcept { - source.end = pos; + return std::string_view{ buffer.c_str() + segments[i].first, segments[i].second }; } TOML_PURE_INLINE_GETTER - std::string_view operator[](size_t i) noexcept + std::string_view back() const noexcept { - return std::string_view{ buffer.c_str() + segments[i].first, segments[i].second }; + return (*this)[segments.size() - 1u]; } TOML_PURE_INLINE_GETTER - std::string_view back() noexcept + bool empty() const noexcept { - return (*this)[segments.size() - 1u]; + return segments.empty(); } TOML_PURE_INLINE_GETTER - size_t size() noexcept + size_t size() const noexcept { return segments.size(); } @@ -1755,19 +1703,18 @@ TOML_IMPL_NAMESPACE_START assert_not_eof(); assert_or_assume(is_bare_key_character(*cp)); - auto& segment = string_buffer; - segment.clear(); + string_buffer.clear(); while (!is_eof()) { if (!is_bare_key_character(*cp)) break; - segment.append(cp->bytes, cp->count); + string_buffer.append(cp->bytes, cp->count); advance_and_return_if_error({}); } - return segment; + return string_buffer; } TOML_NODISCARD @@ -2491,13 +2438,13 @@ TOML_IMPL_NAMESPACE_START } TOML_NODISCARD - array* parse_array(); + node_ptr parse_array(); TOML_NODISCARD - table* parse_inline_table(); + node_ptr parse_inline_table(); TOML_NODISCARD - node* parse_value_known_prefixes() + node_ptr parse_value_known_prefixes() { return_if_error({}); assert_not_eof(); @@ -2515,31 +2462,31 @@ TOML_IMPL_NAMESPACE_START // floats beginning with '.' case U'.': - return new value{ parse_float() }; + return node_ptr{ new value{ parse_float() } }; // strings case U'"': [[fallthrough]]; case U'\'': - return new value{ parse_string().value }; + return node_ptr{ new value{ parse_string().value } }; // bools case U't': [[fallthrough]]; case U'f': [[fallthrough]]; case U'T': [[fallthrough]]; case U'F': - return new value{ parse_boolean() }; + return node_ptr{ new value{ parse_boolean() } }; // inf/nan case U'i': [[fallthrough]]; case U'I': [[fallthrough]]; case U'n': [[fallthrough]]; - case U'N': return new value{ parse_inf_or_nan() }; + case U'N': return node_ptr{ new value{ parse_inf_or_nan() } }; } return nullptr; } TOML_NODISCARD - node* parse_value() + node_ptr parse_value() { return_if_error({}); assert_not_eof(); @@ -2744,7 +2691,7 @@ TOML_IMPL_NAMESPACE_START { if (has_any(begins_zero | begins_digit)) { - val = new value{ static_cast(chars[0] - U'0') }; + val.reset(new value{ static_cast(chars[0] - U'0') }); advance(); // skip the digit break; } @@ -2763,7 +2710,7 @@ TOML_IMPL_NAMESPACE_START // typed parse functions to take over and show better diagnostics if there's an issue // (as opposed to the fallback "could not determine type" message) if (has_any(has_p)) - val = new value{ parse_hex_float() }; + val.reset(new value{ parse_hex_float() }); else if (has_any(has_x | has_o | has_b)) { int64_t i; @@ -2785,17 +2732,17 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - val = new value{ i }; + val.reset(new value{ i }); val->ref_cast().flags(flags); } else if (has_any(has_e) || (has_any(begins_zero | begins_digit) && chars[1] == U'.')) - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); else if (has_any(begins_sign)) { // single-digit signed integers if (char_count == 2u && has_any(has_digits)) { - val = new value{ static_cast(chars[1] - U'0') * (chars[0] == U'-' ? -1LL : 1LL) }; + val.reset(new value{ static_cast(chars[1] - U'0') * (chars[0] == U'-' ? -1LL : 1LL) }); advance(); // skip the sign advance(); // skip the digit break; @@ -2803,11 +2750,11 @@ TOML_IMPL_NAMESPACE_START // simple signed floats (e.g. +1.0) if (is_decimal_digit(chars[1]) && chars[2] == U'.') - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); // signed infinity or nan else if (is_match(chars[1], U'i', U'n', U'I', U'N')) - val = new value{ parse_inf_or_nan() }; + val.reset(new value{ parse_inf_or_nan() }); } return_if_error({}); @@ -2823,14 +2770,14 @@ TOML_IMPL_NAMESPACE_START // binary integers // 0b10 case bzero_msk | has_b: - val = new value{ parse_integer<2>() }; + val.reset(new value{ parse_integer<2>() }); val->ref_cast().flags(value_flags::format_as_binary); break; // octal integers // 0o10 case bzero_msk | has_o: - val = new value{ parse_integer<8>() }; + val.reset(new value{ parse_integer<8>() }); val->ref_cast().flags(value_flags::format_as_octal); break; @@ -2842,12 +2789,12 @@ TOML_IMPL_NAMESPACE_START case bzero_msk: [[fallthrough]]; case bdigit_msk: [[fallthrough]]; case begins_sign | has_digits | has_minus: [[fallthrough]]; - case begins_sign | has_digits | has_plus: val = new value{ parse_integer<10>() }; break; + case begins_sign | has_digits | has_plus: val.reset(new value{ parse_integer<10>() }); break; // hexadecimal integers // 0x10 case bzero_msk | has_x: - val = new value{ parse_integer<16>() }; + val.reset(new value{ parse_integer<16>() }); val->ref_cast().flags(value_flags::format_as_hexadecimal); break; @@ -2900,7 +2847,7 @@ TOML_IMPL_NAMESPACE_START case begins_sign | has_digits | has_e | signs_msk: [[fallthrough]]; case begins_sign | has_digits | has_dot | has_minus: [[fallthrough]]; case begins_sign | has_digits | has_dot | has_e | has_minus: - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); break; // hexadecimal floats @@ -2934,7 +2881,7 @@ TOML_IMPL_NAMESPACE_START case begins_sign | has_digits | has_x | has_dot | has_p | has_minus: [[fallthrough]]; case begins_sign | has_digits | has_x | has_dot | has_p | has_plus: [[fallthrough]]; case begins_sign | has_digits | has_x | has_dot | has_p | signs_msk: - val = new value{ parse_hex_float() }; + val.reset(new value{ parse_hex_float() }); break; // times @@ -2944,12 +2891,12 @@ TOML_IMPL_NAMESPACE_START case bzero_msk | has_colon: [[fallthrough]]; case bzero_msk | has_colon | has_dot: [[fallthrough]]; case bdigit_msk | has_colon: [[fallthrough]]; - case bdigit_msk | has_colon | has_dot: val = new value{ parse_time() }; break; + case bdigit_msk | has_colon | has_dot: val.reset(new value{ parse_time() }); break; // local dates // YYYY-MM-DD case bzero_msk | has_minus: [[fallthrough]]; - case bdigit_msk | has_minus: val = new value{ parse_date() }; break; + case bdigit_msk | has_minus: val.reset(new value{ parse_date() }); break; // date-times // YYYY-MM-DDTHH:MM @@ -2988,7 +2935,7 @@ TOML_IMPL_NAMESPACE_START case bzero_msk | has_minus | has_colon | has_dot | has_z | has_t: [[fallthrough]]; case bdigit_msk | has_minus | has_colon | has_z | has_t: [[fallthrough]]; case bdigit_msk | has_minus | has_colon | has_dot | has_z | has_t: - val = new value{ parse_date_time() }; + val.reset(new value{ parse_date_time() }); break; } } @@ -3012,7 +2959,7 @@ TOML_IMPL_NAMESPACE_START #endif val->source_ = { begin_pos, current_position(1), reader.source_path() }; - return val.release(); + return val; } bool parse_key() @@ -3022,9 +2969,8 @@ TOML_IMPL_NAMESPACE_START assert_or_assume(is_bare_key_character(*cp) || is_string_delimiter(*cp)); push_parse_scope("key"sv); - key_buffer.reset(current_position()); + key_buffer.clear(); recording_whitespace = false; - std::string_view pending_key_segment; while (!is_error()) { @@ -3033,9 +2979,12 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("bare keys may not begin with unicode combining marks"sv); #endif + std::string_view key_segment; + const auto key_begin = current_position(); + // bare_key_segment if (is_bare_key_character(*cp)) - pending_key_segment = parse_bare_key_segment(); + key_segment = parse_bare_key_segment(); // "quoted key segment" else if (is_string_delimiter(*cp)) @@ -3051,12 +3000,12 @@ TOML_IMPL_NAMESPACE_START { set_error_at(begin_pos, "multi-line strings are prohibited in "sv, - key_buffer.segments.empty() ? ""sv : "dotted "sv, + key_buffer.empty() ? ""sv : "dotted "sv, "keys"sv); return_after_error({}); } else - pending_key_segment = str.value; + key_segment = str.value; } // ??? @@ -3065,11 +3014,13 @@ TOML_IMPL_NAMESPACE_START to_sv(*cp), "'"sv); + const auto key_end = current_position(); + // whitespace following the key segment consume_leading_whitespace(); // store segment - key_buffer.push_back(pending_key_segment); + key_buffer.push_back(key_segment, key_begin, key_end); // eof or no more key to come if (is_eof() || *cp != U'.') @@ -3082,10 +3033,20 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - key_buffer.finish(current_position()); return true; } + TOML_NODISCARD + key make_key(size_t segment_index) const + { + TOML_ASSERT(key_buffer.size() > segment_index); + + return key{ + key_buffer[segment_index], + source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path } + }; + } + TOML_NODISCARD table* parse_table_header() { @@ -3154,108 +3115,90 @@ TOML_IMPL_NAMESPACE_START if (!is_eof() && !consume_comment() && !consume_line_break()) set_error_and_return_default("expected a comment or whitespace, saw '"sv, to_sv(cp), "'"sv); } - TOML_ASSERT(!key_buffer.segments.empty()); + TOML_ASSERT(!key_buffer.empty()); // check if each parent is a table/table array, or can be created implicitly as a table. - auto parent = &root; + table* parent = &root; for (size_t i = 0, e = key_buffer.size() - 1u; i < e; i++) { - const auto segment = key_buffer[i]; - auto child = parent->get(segment); - if (!child) - { - child = parent->map_.emplace(segment, new table{}).first->second.get(); - implicit_tables.push_back(&child->ref_cast()); - child->source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - parent = &child->ref_cast
(); - } - else if (child->is_table()) - { - parent = &child->ref_cast
(); - } - else if (child->is_array() - && impl::find(table_arrays.begin(), table_arrays.end(), &child->ref_cast())) - { - // table arrays are a special case; - // the spec dictates we select the most recently declared element in the array. - TOML_ASSERT(!child->ref_cast().elems_.empty()); - TOML_ASSERT(child->ref_cast().elems_.back()->is_table()); - parent = &child->ref_cast().back().ref_cast
(); - } - else - { - if (!is_arr && child->type() == node_type::table) - set_error_and_return_default("cannot redefine existing table '"sv, - to_sv(recording_buffer), - "'"sv); - else - set_error_and_return_default("cannot redefine existing "sv, - to_sv(child->type()), - " '"sv, - to_sv(recording_buffer), - "' as "sv, - is_arr ? "array-of-tables"sv : "table"sv); - } - } + const std::string_view segment = key_buffer[i]; + auto pit = parent->lower_bound(segment); - // check the last parent table for a node matching the last key. - // if there was no matching node, then sweet; - // we can freely instantiate a new table/table array. - const auto last_segment = key_buffer.back(); - auto matching_node = parent->get(last_segment); - if (!matching_node) - { - // if it's an array we need to make the array and it's first table element, - // set the starting regions, and return the table element - if (is_arr) + // parent already existed + if (pit != parent->end() && pit->first == segment) { - array& tbl_arr = parent->emplace(last_segment).first->second.ref_cast(); - table_arrays.push_back(&tbl_arr); - tbl_arr.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + node& p = pit->second; - table& tbl = tbl_arr.emplace_back
(); - tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - return &tbl; + if (auto tbl = p.as_table()) + parent = tbl; + else if (auto arr = p.as_array(); arr && impl::find(table_arrays.begin(), table_arrays.end(), arr)) + { + // table arrays are a special case; + // the spec dictates we select the most recently declared element in the array. + TOML_ASSERT(!arr->elems_.empty()); + TOML_ASSERT(arr->elems_.back()->is_table()); + parent = &arr->back().ref_cast
(); + } + else + { + if (!is_arr && p.type() == node_type::table) + set_error_and_return_default("cannot redefine existing table '"sv, + to_sv(recording_buffer), + "'"sv); + else + set_error_and_return_default("cannot redefine existing "sv, + to_sv(p.type()), + " '"sv, + to_sv(recording_buffer), + "' as "sv, + is_arr ? "array-of-tables"sv : "table"sv); + } } - // otherwise we're just making a table + // need to create a new implicit table else { - table& tbl = parent->emplace
(last_segment).first->second.ref_cast
(); - tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - return &tbl; + pit = parent->emplace_hint
(pit, make_key(i)); + table& p = pit->second.ref_cast
(); + p.source_ = pit->first.source(); + + implicit_tables.push_back(&p); + parent = &p; } } + const auto last_segment = key_buffer.back(); + auto it = parent->lower_bound(last_segment); + // 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. - else + if (it != parent->end() && it->first == last_segment) { - if (is_arr && matching_node->is_array() - && impl::find(table_arrays.begin(), table_arrays.end(), &matching_node->ref_cast())) + node& matching_node = it->second; + if (auto arr = matching_node.as_array(); + is_arr && arr && impl::find(table_arrays.begin(), table_arrays.end(), arr)) { - table& tbl = matching_node->ref_cast().emplace_back
(); + table& tbl = arr->emplace_back
(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; return &tbl; } - else if (!is_arr && matching_node->is_table() && !implicit_tables.empty()) + else if (auto tbl = matching_node.as_table(); !is_arr && tbl && !implicit_tables.empty()) { - table& tbl = matching_node->ref_cast
(); - if (auto found = impl::find(implicit_tables.begin(), implicit_tables.end(), &tbl); - found && (tbl.empty() || tbl.is_homogeneous
())) + if (auto found = impl::find(implicit_tables.begin(), implicit_tables.end(), tbl); + found && (tbl->empty() || tbl->is_homogeneous
())) { implicit_tables.erase(implicit_tables.cbegin() + (found - implicit_tables.data())); - tbl.source_.begin = header_begin_pos; - tbl.source_.end = header_end_pos; - return &tbl; + tbl->source_.begin = header_begin_pos; + tbl->source_.end = header_end_pos; + return tbl; } } // if we get here it's a redefinition error. - if (!is_arr && matching_node->type() == node_type::table) + if (!is_arr && matching_node.type() == node_type::table) { set_error_at(header_begin_pos, "cannot redefine existing table '"sv, @@ -3267,7 +3210,7 @@ TOML_IMPL_NAMESPACE_START { set_error_at(header_begin_pos, "cannot redefine existing "sv, - to_sv(matching_node->type()), + to_sv(matching_node.type()), " '"sv, to_sv(recording_buffer), "' as "sv, @@ -3275,6 +3218,35 @@ TOML_IMPL_NAMESPACE_START return_after_error({}); } } + + // there was no matching node, sweet - we can freely instantiate a new table/table array. + else + { + auto last_key = make_key(key_buffer.size() - 1u); + + // if it's an array we need to make the array and it's first table element, + // set the starting regions, and return the table element + if (is_arr) + { + it = parent->emplace_hint(it, std::move(last_key)); + array& tbl_arr = it->second.ref_cast(); + table_arrays.push_back(&tbl_arr); + tbl_arr.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + + table& tbl = tbl_arr.emplace_back
(); + tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + return &tbl; + } + + // otherwise we're just making a table + else + { + 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() }; + return &tbl; + } + } } bool parse_key_value_pair_and_insert(table* tbl) @@ -3309,58 +3281,67 @@ TOML_IMPL_NAMESPACE_START if (is_value_terminator(*cp)) set_error_and_return_default("expected value, saw '"sv, to_sv(*cp), "'"sv); - // if it's a dotted kvp we need to spawn the sub-tables if necessary, + // if it's a dotted kvp we need to spawn the parent sub-tables if necessary, // and set the target table to the second-to-last one in the chain if (key_buffer.size() > 1u) { for (size_t i = 0; i < key_buffer.size() - 1u; i++) { - const auto segment = key_buffer[i]; - auto child = tbl->get(segment); - if (!child) + const std::string_view segment = key_buffer[i]; + auto pit = tbl->lower_bound(segment); + + // parent already existed + if (pit != tbl->end() && pit->first == segment) { - child = tbl->map_.emplace(std::string{ segment }, new table{}).first->second.get(); - dotted_key_tables.push_back(&child->ref_cast
()); - child->source_ = key_buffer.source; + table* p = pit->second.as_table(); + if (!p + || !(impl::find(dotted_key_tables.begin(), dotted_key_tables.end(), p) + || impl::find(implicit_tables.begin(), implicit_tables.end(), p))) + { + set_error_at(key_buffer.starts[i], + "cannot redefine existing "sv, + to_sv(p->type()), + " as dotted key-value pair"sv); + return_after_error({}); + } + + tbl = p; } - else if (!child->is_table() - || !(impl::find(dotted_key_tables.begin(), - dotted_key_tables.end(), - &child->ref_cast
()) - || impl::find(implicit_tables.begin(), - implicit_tables.end(), - &child->ref_cast
()))) + + // need to create a new implicit table + else { - set_error_at(key_buffer.source.begin, - "cannot redefine existing "sv, - to_sv(child->type()), - " as dotted key-value pair"sv); - return_after_error({}); - } + pit = tbl->emplace_hint
(pit, make_key(i)); + table& p = pit->second.ref_cast
(); + p.source_ = pit->first.source(); - tbl = &child->ref_cast
(); + dotted_key_tables.push_back(&p); + tbl = &p; + } } } // ensure this isn't a redefinition - if (auto conflicting_node = tbl->get(key_buffer.back())) + const std::string_view last_segment = key_buffer.back(); + auto it = tbl->lower_bound(last_segment); + if (it != tbl->end() && it->first == last_segment) { set_error("cannot redefine existing "sv, - to_sv(conflicting_node->type()), + to_sv(it->second.type()), " '"sv, to_sv(recording_buffer), "'"sv); return_after_error({}); } - // cache the last segment since it might get overwritten by nested tables etc. - auto last_segment = std::string{ key_buffer.back() }; + // create the key first since the key buffer will likely get overritten during value parsing (inline tables) + auto last_key = make_key(key_buffer.size() - 1u); // now we can actually parse the value - auto val = node_ptr{ parse_value() }; + node_ptr val = parse_value(); return_if_error({}); - tbl->map_.emplace(std::move(last_segment), std::unique_ptr{ val.release() }); + tbl->map_.emplace_hint(it.raw_, std::move(last_key), std::move(val)); return true; } @@ -3453,8 +3434,7 @@ TOML_IMPL_NAMESPACE_START parser(utf8_reader_interface&& reader_) // : reader{ reader_ } { - root.source_ = { prev_pos, prev_pos, reader.source_path() }; - key_buffer.source.path = reader.source_path(); + root.source_ = { prev_pos, prev_pos, reader.source_path() }; if (!reader.peek_eof()) { @@ -3494,7 +3474,7 @@ TOML_IMPL_NAMESPACE_START }; TOML_EXTERNAL_LINKAGE - array* parser::parse_array() + node_ptr parser::parse_array() { return_if_error({}); assert_not_eof(); @@ -3555,11 +3535,11 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - return reinterpret_cast(arr_ptr.release()); + return arr_ptr; } TOML_EXTERNAL_LINKAGE - table* parser::parse_inline_table() + node_ptr parser::parse_inline_table() { return_if_error({}); assert_not_eof(); @@ -3640,7 +3620,7 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - return reinterpret_cast(tbl_ptr.release()); + return tbl_ptr; } TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS diff --git a/include/toml++/impl/table.h b/include/toml++/impl/table.h index 4bb06ee1..350646f9 100644 --- a/include/toml++/impl/table.h +++ b/include/toml++/impl/table.h @@ -9,6 +9,7 @@ #include "array.h" #include "make_node.h" #include "node_view.h" +#include "key.h" #include "header_start.h" /// \cond @@ -19,7 +20,7 @@ TOML_IMPL_NAMESPACE_START { using value_type = std::conditional_t; - const std::string& first; + const toml::key& first; value_type& second; }; @@ -30,10 +31,11 @@ TOML_IMPL_NAMESPACE_START template friend class table_iterator; friend class TOML_NAMESPACE::table; + friend class TOML_PARSER_TYPENAME; using proxy_type = table_proxy_pair; - using raw_mutable_iterator = std::map, std::less<>>::iterator; - using raw_const_iterator = std::map, std::less<>>::const_iterator; + using raw_mutable_iterator = std::map>::iterator; + using raw_const_iterator = std::map>::const_iterator; using raw_iterator = std::conditional_t; mutable raw_iterator raw_; @@ -154,54 +156,15 @@ TOML_IMPL_NAMESPACE_START struct table_init_pair { - mutable std::string key; - mutable std::unique_ptr value; - - template - TOML_NODISCARD_CTOR - table_init_pair(std::string&& k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ std::move(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(std::string_view k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ k }, - value{ make_node(static_cast(v), flags) } - {} + mutable toml::key key; + mutable node_ptr value; - template + template TOML_NODISCARD_CTOR - table_init_pair(const char* k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ k }, + table_init_pair(K&& k, V&& v, value_flags flags = preserve_source_value_flags) // + : key{ static_cast(k) }, value{ make_node(static_cast(v), flags) } {} - -#if TOML_ENABLE_WINDOWS_COMPAT - - template - TOML_NODISCARD_CTOR - table_init_pair(std::wstring&& k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(std::wstring_view k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(const wchar_t* k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(std::wstring_view{ k }) }, - value{ make_node(static_cast(v), flags) } - {} - -#endif }; } TOML_IMPL_NAMESPACE_END; @@ -228,7 +191,7 @@ TOML_NAMESPACE_START friend class TOML_PARSER_TYPENAME; - std::map, std::less<>> map_; + std::map> map_; bool inline_ = false; TOML_NODISCARD_CTOR @@ -807,56 +770,56 @@ TOML_NAMESPACE_START /// @{ /// \brief Returns an iterator to the first key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER iterator begin() noexcept { - return { map_.begin() }; + return iterator{ map_.begin() }; } /// \brief Returns an iterator to the first key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator begin() const noexcept { - return { map_.cbegin() }; + return const_iterator{ map_.cbegin() }; } /// \brief Returns an iterator to the first key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator cbegin() const noexcept { - return { map_.cbegin() }; + return const_iterator{ map_.cbegin() }; } /// \brief Returns an iterator to one-past-the-last key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER iterator end() noexcept { - return { map_.end() }; + return iterator{ map_.end() }; } /// \brief Returns an iterator to one-past-the-last key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator end() const noexcept { - return { map_.cend() }; + return const_iterator{ map_.cend() }; } /// \brief Returns an iterator to one-past-the-last key-value pair. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator cend() const noexcept { - return { map_.cend() }; + return const_iterator{ map_.cend() }; } /// \brief Returns true if the table is empty. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER bool empty() const noexcept { return map_.empty(); } /// \brief Returns the number of key-value pairs in the table. - TOML_NODISCARD + TOML_PURE_INLINE_GETTER size_t size() const noexcept { return map_.size(); @@ -902,7 +865,7 @@ TOML_NAMESPACE_START /// d = 42 /// \eout /// - /// \tparam KeyType std::string (or a type convertible to it). + /// \tparam KeyType A toml::key or any compatible string type. /// \tparam ValueType toml::node, toml::node_view, toml::table, toml::array, or a native TOML value type /// (or a type promotable to one). /// \param key The key at which to insert the new value. @@ -920,7 +883,7 @@ TOML_NAMESPACE_START /// \attention The return value will always be `{ end(), false }` if the input value was an /// empty toml::node_view, because no insertion can take place. This is the only circumstance /// in which this can occur. - TOML_CONSTRAINED_TEMPLATE((std::is_convertible_v || impl::is_wide_string), + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), typename KeyType, typename ValueType) std::pair insert(KeyType&& key, @@ -947,8 +910,9 @@ TOML_NAMESPACE_START } else { - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), @@ -999,8 +963,7 @@ TOML_NAMESPACE_START /// \remarks This function is morally equivalent to calling `insert(key, value)` for each /// key-value pair covered by the iterator range, so any values with keys already found in the /// table will not be replaced. - TOML_CONSTRAINED_TEMPLATE((!std::is_convertible_v && !impl::is_wide_string), - typename Iter) + TOML_CONSTRAINED_TEMPLATE((!impl::is_key_or_convertible && !impl::is_wide_string), typename Iter) void insert(Iter first, Iter last, value_flags flags = preserve_source_value_flags) { if (first == last) @@ -1049,7 +1012,7 @@ TOML_NAMESPACE_START /// d = 42 /// \eout /// - /// \tparam KeyType std::string (or a type convertible to it). + /// \tparam KeyType A toml::key or any compatible string type. /// \tparam ValueType toml::node, toml::node_view, toml::table, toml::array, or a native TOML value type /// (or a type promotable to one). /// \param key The key at which to insert or assign the value. @@ -1067,7 +1030,9 @@ TOML_NAMESPACE_START /// \attention The return value will always be `{ end(), false }` if the input value was /// an empty toml::node_view, because no insertion or assignment can take place. /// This is the only circumstance in which this can occur. - template + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename KeyType, + typename ValueType) std::pair insert_or_assign(KeyType&& key, ValueType&& val, value_flags flags = preserve_source_value_flags) @@ -1094,8 +1059,9 @@ TOML_NAMESPACE_START } else { - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), @@ -1104,12 +1070,117 @@ TOML_NAMESPACE_START } else { - (*ipos).second.reset(impl::make_node(static_cast(val), flags)); + (*ipos).second = impl::make_node(static_cast(val), flags); return { iterator{ ipos }, false }; } } } + /// \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(). + TOML_PURE_GETTER + TOML_API + iterator lower_bound(std::string_view key) noexcept; + + /// \brief Returns a const 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(). + TOML_PURE_GETTER + TOML_API + const_iterator lower_bound(std::string_view key) const noexcept; + +#if TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Returns an iterator to the first key-value pair with key that is _not less_ than the given key. + /// + /// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. + /// + /// \returns An iterator to the first matching key-value pair, or #end(). + TOML_NODISCARD + iterator lower_bound(std::wstring_view key) + { + return lower_bound(impl::narrow(key)); + } + + /// \brief Returns a const iterator to the first key-value pair with key that is _not less_ than the given key. + /// + /// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled. + /// + /// \returns An iterator to the first matching key-value pair, or #end(). + TOML_NODISCARD + const_iterator lower_bound(std::wstring_view key) const + { + return lower_bound(impl::narrow(key)); + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + /// \brief Emplaces a new value at a specific key if one did not already exist. + /// + /// \tparam ValueType toml::table, toml::array, or any native TOML value type. + /// \tparam KeyType A toml::key or any compatible string type. + /// \tparam ValueArgs Value constructor argument types. + /// \param hint Iterator to the position before which the new element will be emplaced. + /// \param key The key at which to emplace the new value. + /// \param args Arguments to forward to the value's constructor. + /// + /// \returns An iterator to the emplacement position (or the position of the value that prevented emplacement) + /// + /// \note This function has exactly the same semantics as [std::map::emplace_hint()](https://en.cppreference.com/w/cpp/container/map/emplace_hint). + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename ValueType, + typename KeyType, + typename... ValueArgs) + iterator emplace_hint(const_iterator hint, KeyType&& key, ValueArgs&&... args) + { + static_assert(!impl::is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, + "Emplacement using wide-character keys is only supported on Windows with " + "TOML_ENABLE_WINDOWS_COMPAT enabled."); + + static_assert(!impl::is_cvref, "ValueType may not be const, volatile, or a reference."); + + if constexpr (impl::is_wide_string) + { +#if TOML_ENABLE_WINDOWS_COMPAT + return emplace_hint(hint, + impl::narrow(static_cast(key)), + static_cast(args)...); +#else + static_assert(impl::dependent_false, "Evaluated unreachable branch!"); +#endif + } + else + { + using unwrapped_type = impl::unwrap_node; + static_assert((impl::is_native || impl::is_one_of), + "ValueType argument of table::emplace_hint() must be one " + "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); + + auto ipos = map_.emplace_hint(hint.raw_, static_cast(key), nullptr); + + // if second is nullptr then we successully claimed the key and inserted the empty sentinel, + // so now we have to construct the actual value + if (!ipos->second) + { +#if TOML_COMPILER_EXCEPTIONS + try + { + ipos->second.reset(new impl::wrap_node{ static_cast(args)... }); + } + catch (...) + { + map_.erase(ipos); // strong exception guarantee + throw; + } +#else + ipos->second.reset(new impl::wrap_node{ static_cast(args)... }); +#endif + } + return ipos; + } + } + /// \brief Emplaces a new value at a specific key if one did not already exist. /// /// \detail \cpp @@ -1138,7 +1209,7 @@ TOML_NAMESPACE_START /// \eout /// /// \tparam ValueType toml::table, toml::array, or any native TOML value type. - /// \tparam KeyType std::string (or a type convertible to it). + /// \tparam KeyType A toml::key or any compatible string type. /// \tparam ValueArgs Value constructor argument types. /// \param key The key at which to emplace the new value. /// \param args Arguments to forward to the value's constructor. @@ -1148,13 +1219,18 @@ TOML_NAMESPACE_START /// - A boolean indicating if the emplacement was successful. /// /// \remark There is no difference between insert() and emplace() for trivial value types (floats, ints, bools). - template + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename ValueType, + typename KeyType, + typename... ValueArgs) std::pair emplace(KeyType&& key, ValueArgs&&... args) { static_assert(!impl::is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, "Emplacement using wide-character keys is only supported on Windows with " "TOML_ENABLE_WINDOWS_COMPAT enabled."); + static_assert(!impl::is_cvref, "ValueType may not be const, volatile, or a reference."); + if constexpr (impl::is_wide_string) { #if TOML_ENABLE_WINDOWS_COMPAT @@ -1165,17 +1241,18 @@ TOML_NAMESPACE_START } else { - using type = impl::unwrap_node; - static_assert((impl::is_native || impl::is_one_of)&&!impl::is_cvref, - "The emplacement type argument of table::emplace() must be one " + using unwrapped_type = impl::unwrap_node; + static_assert((impl::is_native || impl::is_one_of), + "ValueType argument of table::emplace() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), - new impl::wrap_node{ static_cast(args)... }); + new impl::wrap_node{ static_cast(args)... }); return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; diff --git a/include/toml++/impl/table.inl b/include/toml++/impl/table.inl index d31d06ce..ebe0a766 100644 --- a/include/toml++/impl/table.inl +++ b/include/toml++/impl/table.inl @@ -38,8 +38,8 @@ TOML_NAMESPACE_START : node(other), inline_{ other.inline_ } { - for (auto&& [k, v] : other) - map_.emplace_hint(map_.end(), k, impl::make_node(v)); + for (auto&& [k, v] : other.map_) + map_.emplace_hint(map_.end(), k, impl::make_node(*v)); #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -64,8 +64,8 @@ TOML_NAMESPACE_START { node::operator=(rhs); map_.clear(); - for (auto&& [k, v] : rhs) - map_.emplace_hint(map_.end(), k, impl::make_node(v)); + for (auto&& [k, v] : rhs.map_) + map_.emplace_hint(map_.end(), k, impl::make_node(*v)); inline_ = rhs.inline_; } return *this; @@ -92,9 +92,9 @@ TOML_NAMESPACE_START if (ntype == node_type::none) ntype = map_.cbegin()->second->type(); - for (const auto& [k, v] : map_) + for (auto&& [k, v] : map_) { - (void)k; + static_cast(k); if (v->type() != ntype) return false; } @@ -114,7 +114,7 @@ TOML_NAMESPACE_START ntype = map_.cbegin()->second->type(); for (const auto& [k, v] : map_) { - (void)k; + static_cast(k); if (v->type() != ntype) { first_nonmatch = v.get(); @@ -192,6 +192,18 @@ TOML_NAMESPACE_START } return true; } + + TOML_EXTERNAL_LINKAGE + table::iterator table::lower_bound(std::string_view key) noexcept + { + return iterator{ map_.lower_bound(key) }; + } + + TOML_EXTERNAL_LINKAGE + table::const_iterator table::lower_bound(std::string_view key) const noexcept + { + return const_iterator{ map_.lower_bound(key) }; + } } TOML_NAMESPACE_END; diff --git a/include/toml++/impl/toml_formatter.h b/include/toml++/impl/toml_formatter.h index 39b9e7a2..affdfb87 100644 --- a/include/toml++/impl/toml_formatter.h +++ b/include/toml++/impl/toml_formatter.h @@ -47,17 +47,14 @@ TOML_NAMESPACE_START /// \cond using base = impl::formatter; - std::vector key_path_; + std::vector key_path_; bool pending_table_separator_ = false; TOML_API void print_pending_table_separator(); TOML_API - void print_key_segment(std::string_view); - - TOML_API - void print_key_path(); + void print(const key&); TOML_API void print_inline(const toml::table&); diff --git a/include/toml++/impl/toml_formatter.inl b/include/toml++/impl/toml_formatter.inl index 4ad427e6..10deefe9 100644 --- a/include/toml++/impl/toml_formatter.inl +++ b/include/toml++/impl/toml_formatter.inl @@ -128,20 +128,9 @@ TOML_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void toml_formatter::print_key_segment(std::string_view str) + void toml_formatter::print(const key& k) { - print_string(str, false, true); - } - - TOML_EXTERNAL_LINKAGE - void toml_formatter::print_key_path() - { - for (const auto& segment : key_path_) - { - if (std::addressof(segment) > key_path_.data()) - print_unformatted('.'); - print_key_segment(segment); - } + print_string(k.str(), false, true); } TOML_EXTERNAL_LINKAGE @@ -162,7 +151,7 @@ TOML_NAMESPACE_START print_unformatted(", "sv); first = true; - print_key_segment(k); + print(k); print_unformatted(" = "sv); const auto type = v.type(); @@ -262,7 +251,7 @@ TOML_NAMESPACE_START pending_table_separator_ = true; print_newline(); print_indent(); - print_key_segment(k); + print(k); print_unformatted(" = "sv); TOML_ASSUME(type != node_type::none); switch (type) @@ -273,6 +262,17 @@ TOML_NAMESPACE_START } } + const auto print_key_path = [&]() + { + size_t i{}; + for (const auto k : key_path_) + { + if (i++) + print_unformatted('.'); + print(*k); + } + }; + // non-inline tables for (auto&& [k, v] : tbl) { @@ -314,7 +314,7 @@ TOML_NAMESPACE_START if (child_value_count == 0u && (child_table_count > 0u || child_table_array_count > 0u)) skip_self = true; - key_path_.push_back(std::string_view{ k }); + key_path_.push_back(&k); if (!skip_self) { @@ -344,7 +344,7 @@ TOML_NAMESPACE_START if (indent_sub_tables()) increase_indent(); - key_path_.push_back(std::string_view{ k }); + key_path_.push_back(&k); for (size_t i = 0; i < arr.size(); i++) { diff --git a/include/toml++/impl/value.h b/include/toml++/impl/value.h index e2354eb2..bddea6a0 100644 --- a/include/toml++/impl/value.h +++ b/include/toml++/impl/value.h @@ -715,7 +715,7 @@ TOML_NAMESPACE_START return *this; } - /// \name Equality + /// \name Equality and Comparison /// @{ /// \brief Value equality operator. diff --git a/include/toml++/impl/yaml_formatter.inl b/include/toml++/impl/yaml_formatter.inl index bd7bb848..fc910f26 100644 --- a/include/toml++/impl/yaml_formatter.inl +++ b/include/toml++/impl/yaml_formatter.inl @@ -82,7 +82,7 @@ TOML_NAMESPACE_START } parent_is_array = false; - print_string(k, false, true); + print_string(k.str(), false, true); print_unformatted(": "sv); const auto type = v.type(); diff --git a/tests/parsing_tables.cpp b/tests/parsing_tables.cpp index ebc050ea..8b95f8e6 100644 --- a/tests/parsing_tables.cpp +++ b/tests/parsing_tables.cpp @@ -551,3 +551,45 @@ fruit = [] color = "green" )"sv); } + +TEST_CASE("parsing - keys") +{ + parsing_should_succeed(FILE_LINE_ARGS, + R"( +[a.b] +c = "10.0.0.1" +d = "frontend" +e = { f.g = 79.5, h = 72.0 } + )"sv, + [](table&& tbl) + { + // ensure types are sane first + REQUIRE(tbl["a"].is_table()); + REQUIRE(tbl["a"]["b"].is_table()); + REQUIRE(tbl["a"]["b"]["c"]); + REQUIRE(tbl["a"]["b"]["d"]); + REQUIRE(tbl["a"]["b"]["e"].is_table()); + REQUIRE(tbl["a"]["b"]["e"]["f"].is_table()); + REQUIRE(tbl["a"]["b"]["e"]["f"]["g"]); + REQUIRE(tbl["a"]["b"]["e"]["h"]); + + const auto check_key = + [&](const auto& t, std::string_view k, source_position b, source_position e) + { + const toml::key& found_key = t.as_table()->find(k)->first; + CHECK(found_key.str() == k); + CHECK(found_key.source().begin == b); + CHECK(found_key.source().end == e); + CHECK(found_key.source().path == tbl.source().path); + }; + + check_key(tbl, "a", { 2, 2 }, { 2, 3 }); + check_key(tbl["a"], "b", { 2, 4 }, { 2, 5 }); + check_key(tbl["a"]["b"], "c", { 3, 1 }, { 3, 2 }); + check_key(tbl["a"]["b"], "d", { 4, 1 }, { 4, 2 }); + check_key(tbl["a"]["b"], "e", { 5, 1 }, { 5, 2 }); + check_key(tbl["a"]["b"]["e"], "f", { 5, 7 }, { 5, 8 }); + check_key(tbl["a"]["b"]["e"]["f"], "g", { 5, 9 }, { 5, 10 }); + check_key(tbl["a"]["b"]["e"], "h", { 5, 19 }, { 5, 20 }); + }); +} diff --git a/toml++.natvis b/toml++.natvis index 0cc50d2d..dbfb9e4e 100644 --- a/toml++.natvis +++ b/toml++.natvis @@ -69,25 +69,36 @@ + + {{ {val_,s8} }} + + val_,s8 + + + {{ {val_} }} - val_ + val_ - - {{ {val_,s8} }} + + line {line,d}, column {column,d} - val_,s8 + line,d + column,d - - line {line}, column {column} + + + {key_,s8} ({source_.begin}) + {key_,s8} - line - column + key_,s8 + source_.begin + source_.end diff --git a/toml++.vcxproj b/toml++.vcxproj index ddf8a7fc..2c317a63 100644 --- a/toml++.vcxproj +++ b/toml++.vcxproj @@ -38,6 +38,7 @@ + @@ -125,4 +126,4 @@ - + \ No newline at end of file diff --git a/toml++.vcxproj.filters b/toml++.vcxproj.filters index b1da86d0..b14e6918 100644 --- a/toml++.vcxproj.filters +++ b/toml++.vcxproj.filters @@ -133,6 +133,9 @@ include\impl + + include\impl + diff --git a/toml.hpp b/toml.hpp index 67a72300..d55bffd0 100644 --- a/toml.hpp +++ b/toml.hpp @@ -1103,6 +1103,7 @@ TOML_NAMESPACE_START template class node_view; + class key; class array; class table; template @@ -1125,6 +1126,8 @@ TOML_NAMESPACE_END; TOML_IMPL_NAMESPACE_START { + using node_ptr = std::unique_ptr; + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); class parser; TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS @@ -2122,21 +2125,21 @@ TOML_NAMESPACE_START day{ static_cast(d) } {} - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const date& lhs, const date& rhs) noexcept { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const date& lhs, const date& rhs) noexcept { return lhs.year != rhs.year || lhs.month != rhs.month || lhs.day != rhs.day; } private: - TOML_NODISCARD - TOML_ALWAYS_INLINE + + TOML_PURE_GETTER static constexpr uint32_t pack(const date& d) noexcept { return (static_cast(d.year) << 16) | (static_cast(d.month) << 8) @@ -2145,25 +2148,25 @@ TOML_NAMESPACE_START public: - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const date& lhs, const date& rhs) noexcept { return pack(lhs) < pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const date& lhs, const date& rhs) noexcept { return pack(lhs) <= pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const date& lhs, const date& rhs) noexcept { return pack(lhs) > pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const date& lhs, const date& rhs) noexcept { return pack(lhs) >= pack(rhs); @@ -2202,22 +2205,22 @@ TOML_NAMESPACE_START nanosecond{ static_cast(ns) } {} - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const time& lhs, const time& rhs) noexcept { return lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second && lhs.nanosecond == rhs.nanosecond; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const time& lhs, const time& rhs) noexcept { return !(lhs == rhs); } private: - TOML_NODISCARD - TOML_ALWAYS_INLINE + + TOML_PURE_GETTER static constexpr uint64_t pack(const time& t) noexcept { return static_cast(t.hour) << 48 | static_cast(t.minute) << 40 @@ -2226,25 +2229,25 @@ TOML_NAMESPACE_START public: - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const time& lhs, const time& rhs) noexcept { return pack(lhs) < pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const time& lhs, const time& rhs) noexcept { return pack(lhs) <= pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const time& lhs, const time& rhs) noexcept { return pack(lhs) > pack(rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const time& lhs, const time& rhs) noexcept { return pack(lhs) >= pack(rhs); @@ -2272,37 +2275,37 @@ TOML_NAMESPACE_START + static_cast>(m)) } {} - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes == rhs.minutes; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes != rhs.minutes; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes < rhs.minutes; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes <= rhs.minutes; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes > rhs.minutes; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(time_offset lhs, time_offset rhs) noexcept { return lhs.minutes >= rhs.minutes; @@ -2342,25 +2345,25 @@ TOML_NAMESPACE_START offset{ off } {} - TOML_NODISCARD + TOML_PURE_GETTER constexpr bool is_local() const noexcept { return !offset.has_value(); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator==(const date_time& lhs, const date_time& rhs) noexcept { return lhs.date == rhs.date && lhs.time == rhs.time && lhs.offset == rhs.offset; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator!=(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs == rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<(const date_time& lhs, const date_time& rhs) noexcept { if (lhs.date != rhs.date) @@ -2370,7 +2373,7 @@ TOML_NAMESPACE_START return lhs.offset < rhs.offset; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator<=(const date_time& lhs, const date_time& rhs) noexcept { if (lhs.date != rhs.date) @@ -2380,13 +2383,13 @@ TOML_NAMESPACE_START return lhs.offset <= rhs.offset; } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs <= rhs); } - TOML_NODISCARD + TOML_PURE_GETTER friend constexpr bool operator>=(const date_time& lhs, const date_time& rhs) noexcept { return !(lhs < rhs); @@ -3273,7 +3276,7 @@ TOML_NAMESPACE_START } TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, const toml::value&, template ); - TOML_CONSTRAINED_TEMPLATE((impl::is_native || impl::is_losslessly_convertible_to_native), typename T) + TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native, typename T) TOML_NODISCARD friend bool operator==(const node_view& lhs, const T& rhs) noexcept(!impl::is_wide_string) { @@ -3295,11 +3298,10 @@ TOML_NAMESPACE_START return val && *val == rhs; } } - TOML_ASYMMETRICAL_EQUALITY_OPS( - const node_view&, - const T&, - TOML_CONSTRAINED_TEMPLATE((impl::is_native || impl::is_losslessly_convertible_to_native), - typename T)); + TOML_ASYMMETRICAL_EQUALITY_OPS(const node_view&, + const T&, + TOML_CONSTRAINED_TEMPLATE(impl::is_losslessly_convertible_to_native, + typename T)); template TOML_NODISCARD @@ -4573,7 +4575,7 @@ TOML_IMPL_NAMESPACE_START template TOML_NODISCARD TOML_ATTR(returns_nonnull) - auto* make_node_specialized(T && val, [[maybe_unused]] value_flags flags) + auto* make_node_impl_specialized(T && val, [[maybe_unused]] value_flags flags) { using unwrapped_type = unwrap_node>; static_assert(!std::is_same_v); @@ -4605,8 +4607,20 @@ TOML_IMPL_NAMESPACE_START static_assert(!is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, "Instantiating values from wide-character strings is only " "supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled."); - static_assert(is_native || is_losslessly_convertible_to_native, - "Value initializers must be (or be promotable to) one of the TOML value types"); + + if constexpr (!is_losslessly_convertible_to_native) + { + if constexpr (std::is_same_v) + static_assert(dependent_false, + "Integral value initializers must be losslessly convertible to int64_t"); + else if constexpr (std::is_same_v) + static_assert(dependent_false, + "Floating-point value initializers must be losslessly convertible to double"); + else + static_assert( + dependent_false, + "Value initializers must be losslessly convertible to one of the TOML value types"); + } if constexpr (is_wide_string) { @@ -4629,12 +4643,12 @@ TOML_IMPL_NAMESPACE_START template TOML_NODISCARD - auto* make_node(T && val, value_flags flags = preserve_source_value_flags) + auto* make_node_impl(T && val, value_flags flags = preserve_source_value_flags) { - using type = unwrap_node>; - if constexpr (std::is_same_v || is_node_view) + using unwrapped_type = unwrap_node>; + if constexpr (std::is_same_v || is_node_view) { - if constexpr (is_node_view) + if constexpr (is_node_view) { if (!val) return static_cast(nullptr); @@ -4643,24 +4657,24 @@ TOML_IMPL_NAMESPACE_START return static_cast(val).visit( [flags](auto&& concrete) { return static_cast( - make_node_specialized(static_cast(concrete), flags)); + make_node_impl_specialized(static_cast(concrete), flags)); }); } else - return make_node_specialized(static_cast(val), flags); + return make_node_impl_specialized(static_cast(val), flags); } template TOML_NODISCARD - auto* make_node(inserter && val, value_flags flags = preserve_source_value_flags) + auto* make_node_impl(inserter && val, value_flags flags = preserve_source_value_flags) { - return make_node(static_cast(val.value), flags); + return make_node_impl(static_cast(val.value), flags); } template || is_node_view || is_value || can_partially_represent_native)> struct inserted_type_of_ { - using type = std::remove_pointer_t()))>; + using type = std::remove_pointer_t()))>; }; template @@ -4674,6 +4688,13 @@ TOML_IMPL_NAMESPACE_START { using type = void; }; + + template + TOML_NODISCARD + node_ptr make_node(T && val, value_flags flags = preserve_source_value_flags) + { + return node_ptr{ make_node_impl(static_cast(val), flags) }; + } } TOML_IMPL_NAMESPACE_END; @@ -4710,8 +4731,8 @@ TOML_IMPL_NAMESPACE_START friend class array_iterator; friend class TOML_NAMESPACE::array; - using raw_mutable_iterator = std::vector>::iterator; - using raw_const_iterator = std::vector>::const_iterator; + using raw_mutable_iterator = std::vector::iterator; + using raw_const_iterator = std::vector::const_iterator; using raw_iterator = std::conditional_t; mutable raw_iterator raw_; @@ -4883,7 +4904,7 @@ TOML_NAMESPACE_START private: friend class TOML_PARSER_TYPENAME; - std::vector> elems_; + std::vector elems_; TOML_API void preinsertion_resize(size_t idx, size_t count); @@ -5360,9 +5381,9 @@ TOML_NAMESPACE_START preinsertion_resize(start_idx, count); size_t i = start_idx; for (size_t e = start_idx + count - 1u; i < e; i++) - elems_[i].reset(impl::make_node(val, flags)); + elems_[i] = impl::make_node(val, flags); - elems_[i].reset(impl::make_node(static_cast(val), flags)); + elems_[i] = impl::make_node(static_cast(val), flags); return { elems_.begin() + static_cast(start_idx) }; } } @@ -5397,9 +5418,9 @@ TOML_NAMESPACE_START continue; } if constexpr (std::is_rvalue_reference_v) - elems_[i++].reset(impl::make_node(std::move(*it), flags)); + elems_[i++] = impl::make_node(std::move(*it), flags); else - elems_[i++].reset(impl::make_node(*it, flags)); + elems_[i++] = impl::make_node(*it, flags); } return { elems_.begin() + static_cast(start_idx) }; } @@ -5435,7 +5456,7 @@ TOML_NAMESPACE_START } const auto it = elems_.begin() + (pos.raw_ - elems_.cbegin()); - it->reset(impl::make_node(static_cast(val), flags)); + *it = impl::make_node(static_cast(val), flags); return iterator{ it }; } @@ -5514,8 +5535,8 @@ TOML_NAMESPACE_START static bool equal_to_container(const array& lhs, const T& rhs) noexcept { using element_type = std::remove_const_t; - static_assert(impl::is_native || impl::is_losslessly_convertible_to_native, - "Container element type must be (or be promotable to) one of the TOML value types"); + static_assert(impl::is_losslessly_convertible_to_native, + "Container element type must be losslessly convertible one of the native TOML value types"); if (lhs.size() != rhs.size()) return false; @@ -5596,6 +5617,238 @@ TOML_PUSH_WARNINGS; #undef max #endif +TOML_NAMESPACE_START +{ + class key + { + private: + std::string key_; + source_region source_; + + public: + + key() noexcept = default; + + explicit key(std::string_view k, source_region&& src = {}) // + : key_{ k }, + source_{ std::move(src) } + {} + + explicit key(std::string_view k, const source_region& src) // + : key_{ k }, + source_{ src } + {} + + explicit key(std::string&& k, source_region&& src = {}) noexcept // + : key_{ std::move(k) }, + source_{ std::move(src) } + {} + + explicit key(std::string&& k, const source_region& src) noexcept // + : key_{ std::move(k) }, + source_{ src } + {} + + explicit key(const char* k, source_region&& src = {}) // + : key_{ k }, + source_{ std::move(src) } + {} + + explicit key(const char* k, const source_region& src) // + : key_{ k }, + source_{ src } + {} + +#if TOML_ENABLE_WINDOWS_COMPAT + + explicit key(std::wstring_view k, source_region&& src = {}) // + : key_{ impl::narrow(k) }, + source_{ std::move(src) } + {} + + explicit key(std::wstring_view k, const source_region& src) // + : key_{ impl::narrow(k) }, + source_{ src } + {} + +#endif + + TOML_PURE_INLINE_GETTER + std::string_view str() const noexcept + { + return std::string_view{ key_ }; + } + + TOML_PURE_INLINE_GETTER + /*implicit*/ operator std::string_view() const noexcept + { + return str(); + } + + TOML_PURE_INLINE_GETTER + bool empty() const noexcept + { + return key_.empty(); + } + + TOML_PURE_INLINE_GETTER + const char* data() const noexcept + { + return key_.data(); + } + + TOML_PURE_INLINE_GETTER + size_t length() const noexcept + { + return key_.length(); + } + + TOML_PURE_INLINE_GETTER + const source_region& source() const noexcept + { + return source_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator==(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ == rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator!=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ != rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ < rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ <= rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ > rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>=(const key& lhs, const key& rhs) noexcept + { + return lhs.key_ >= rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator==(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ == rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator!=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ != rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ < rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ <= rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ > rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>=(const key& lhs, std::string_view rhs) noexcept + { + return lhs.key_ >= rhs; + } + + TOML_PURE_INLINE_GETTER + friend bool operator==(std::string_view lhs, const key& rhs) noexcept + { + return lhs == rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator!=(std::string_view lhs, const key& rhs) noexcept + { + return lhs != rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<(std::string_view lhs, const key& rhs) noexcept + { + return lhs < rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator<=(std::string_view lhs, const key& rhs) noexcept + { + return lhs <= rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>(std::string_view lhs, const key& rhs) noexcept + { + return lhs > rhs.key_; + } + + TOML_PURE_INLINE_GETTER + friend bool operator>=(std::string_view lhs, const key& rhs) noexcept + { + return lhs >= rhs.key_; + } + + friend std::ostream& operator<<(std::ostream& lhs, const key& rhs) + { + impl::print_to_stream(lhs, rhs.key_); + return lhs; + } + }; +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + template + inline constexpr bool is_key = std::is_same_v, toml::key>; + + template + inline constexpr bool is_key_or_convertible = is_key || std::is_constructible_v; +} +TOML_IMPL_NAMESPACE_END; + +#ifdef _MSC_VER +#pragma pop_macro("min") +#pragma pop_macro("max") +#endif +TOML_POP_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + TOML_IMPL_NAMESPACE_START { template @@ -5603,7 +5856,7 @@ TOML_IMPL_NAMESPACE_START { using value_type = std::conditional_t; - const std::string& first; + const toml::key& first; value_type& second; }; @@ -5614,10 +5867,11 @@ TOML_IMPL_NAMESPACE_START template friend class table_iterator; friend class TOML_NAMESPACE::table; + friend class TOML_PARSER_TYPENAME; using proxy_type = table_proxy_pair; - using raw_mutable_iterator = std::map, std::less<>>::iterator; - using raw_const_iterator = std::map, std::less<>>::const_iterator; + using raw_mutable_iterator = std::map>::iterator; + using raw_const_iterator = std::map>::const_iterator; using raw_iterator = std::conditional_t; mutable raw_iterator raw_; @@ -5738,54 +5992,15 @@ TOML_IMPL_NAMESPACE_START struct table_init_pair { - mutable std::string key; - mutable std::unique_ptr value; - - template - TOML_NODISCARD_CTOR - table_init_pair(std::string&& k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ std::move(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(std::string_view k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ k }, - value{ make_node(static_cast(v), flags) } - {} + mutable toml::key key; + mutable node_ptr value; - template + template TOML_NODISCARD_CTOR - table_init_pair(const char* k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ k }, + table_init_pair(K&& k, V&& v, value_flags flags = preserve_source_value_flags) // + : key{ static_cast(k) }, value{ make_node(static_cast(v), flags) } {} - -#if TOML_ENABLE_WINDOWS_COMPAT - - template - TOML_NODISCARD_CTOR - table_init_pair(std::wstring&& k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(std::wstring_view k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(k) }, - value{ make_node(static_cast(v), flags) } - {} - - template - TOML_NODISCARD_CTOR - table_init_pair(const wchar_t* k, V&& v, value_flags flags = preserve_source_value_flags) // - : key{ narrow(std::wstring_view{ k }) }, - value{ make_node(static_cast(v), flags) } - {} - -#endif }; } TOML_IMPL_NAMESPACE_END; @@ -5802,7 +6017,7 @@ TOML_NAMESPACE_START friend class TOML_PARSER_TYPENAME; - std::map, std::less<>> map_; + std::map> map_; bool inline_ = false; TOML_NODISCARD_CTOR @@ -6161,49 +6376,49 @@ TOML_NAMESPACE_START #endif // TOML_ENABLE_WINDOWS_COMPAT - TOML_NODISCARD + TOML_PURE_INLINE_GETTER iterator begin() noexcept { - return { map_.begin() }; + return iterator{ map_.begin() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator begin() const noexcept { - return { map_.cbegin() }; + return const_iterator{ map_.cbegin() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator cbegin() const noexcept { - return { map_.cbegin() }; + return const_iterator{ map_.cbegin() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER iterator end() noexcept { - return { map_.end() }; + return iterator{ map_.end() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator end() const noexcept { - return { map_.cend() }; + return const_iterator{ map_.cend() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER const_iterator cend() const noexcept { - return { map_.cend() }; + return const_iterator{ map_.cend() }; } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER bool empty() const noexcept { return map_.empty(); } - TOML_NODISCARD + TOML_PURE_INLINE_GETTER size_t size() const noexcept { return map_.size(); @@ -6214,7 +6429,7 @@ TOML_NAMESPACE_START map_.clear(); } - TOML_CONSTRAINED_TEMPLATE((std::is_convertible_v || impl::is_wide_string), + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), typename KeyType, typename ValueType) std::pair insert(KeyType&& key, @@ -6241,8 +6456,9 @@ TOML_NAMESPACE_START } else { - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), @@ -6253,8 +6469,7 @@ TOML_NAMESPACE_START } } - TOML_CONSTRAINED_TEMPLATE((!std::is_convertible_v && !impl::is_wide_string), - typename Iter) + TOML_CONSTRAINED_TEMPLATE((!impl::is_key_or_convertible && !impl::is_wide_string), typename Iter) void insert(Iter first, Iter last, value_flags flags = preserve_source_value_flags) { if (first == last) @@ -6268,7 +6483,9 @@ TOML_NAMESPACE_START } } - template + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename KeyType, + typename ValueType) std::pair insert_or_assign(KeyType&& key, ValueType&& val, value_flags flags = preserve_source_value_flags) @@ -6295,8 +6512,9 @@ TOML_NAMESPACE_START } else { - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), @@ -6305,19 +6523,101 @@ TOML_NAMESPACE_START } else { - (*ipos).second.reset(impl::make_node(static_cast(val), flags)); + (*ipos).second = impl::make_node(static_cast(val), flags); return { iterator{ ipos }, false }; } } } - template + TOML_PURE_GETTER + TOML_API + iterator lower_bound(std::string_view key) noexcept; + + TOML_PURE_GETTER + TOML_API + const_iterator lower_bound(std::string_view key) const noexcept; + +#if TOML_ENABLE_WINDOWS_COMPAT + + TOML_NODISCARD + iterator lower_bound(std::wstring_view key) + { + return lower_bound(impl::narrow(key)); + } + + TOML_NODISCARD + const_iterator lower_bound(std::wstring_view key) const + { + return lower_bound(impl::narrow(key)); + } + +#endif // TOML_ENABLE_WINDOWS_COMPAT + + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename ValueType, + typename KeyType, + typename... ValueArgs) + iterator emplace_hint(const_iterator hint, KeyType&& key, ValueArgs&&... args) + { + static_assert(!impl::is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, + "Emplacement using wide-character keys is only supported on Windows with " + "TOML_ENABLE_WINDOWS_COMPAT enabled."); + + static_assert(!impl::is_cvref, "ValueType may not be const, volatile, or a reference."); + + if constexpr (impl::is_wide_string) + { +#if TOML_ENABLE_WINDOWS_COMPAT + return emplace_hint(hint, + impl::narrow(static_cast(key)), + static_cast(args)...); +#else + static_assert(impl::dependent_false, "Evaluated unreachable branch!"); +#endif + } + else + { + using unwrapped_type = impl::unwrap_node; + static_assert((impl::is_native || impl::is_one_of), + "ValueType argument of table::emplace_hint() must be one " + "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); + + auto ipos = map_.emplace_hint(hint.raw_, static_cast(key), nullptr); + + // if second is nullptr then we successully claimed the key and inserted the empty sentinel, + // so now we have to construct the actual value + if (!ipos->second) + { +#if TOML_COMPILER_EXCEPTIONS + try + { + ipos->second.reset(new impl::wrap_node{ static_cast(args)... }); + } + catch (...) + { + map_.erase(ipos); // strong exception guarantee + throw; + } +#else + ipos->second.reset(new impl::wrap_node{ static_cast(args)... }); +#endif + } + return ipos; + } + } + + TOML_CONSTRAINED_TEMPLATE((impl::is_key_or_convertible || impl::is_wide_string), + typename ValueType, + typename KeyType, + typename... ValueArgs) std::pair emplace(KeyType&& key, ValueArgs&&... args) { static_assert(!impl::is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, "Emplacement using wide-character keys is only supported on Windows with " "TOML_ENABLE_WINDOWS_COMPAT enabled."); + static_assert(!impl::is_cvref, "ValueType may not be const, volatile, or a reference."); + if constexpr (impl::is_wide_string) { #if TOML_ENABLE_WINDOWS_COMPAT @@ -6328,17 +6628,18 @@ TOML_NAMESPACE_START } else { - using type = impl::unwrap_node; - static_assert((impl::is_native || impl::is_one_of)&&!impl::is_cvref, - "The emplacement type argument of table::emplace() must be one " + using unwrapped_type = impl::unwrap_node; + static_assert((impl::is_native || impl::is_one_of), + "ValueType argument of table::emplace() must be one " "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST); - auto ipos = map_.lower_bound(key); - if (ipos == map_.end() || ipos->first != key) + const auto key_view = std::string_view{ key }; + auto ipos = map_.lower_bound(key_view); + if (ipos == map_.end() || ipos->first != key_view) { ipos = map_.emplace_hint(ipos, static_cast(key), - new impl::wrap_node{ static_cast(args)... }); + new impl::wrap_node{ static_cast(args)... }); return { iterator{ ipos }, true }; } return { iterator{ ipos }, false }; @@ -8159,17 +8460,14 @@ TOML_NAMESPACE_START private: using base = impl::formatter; - std::vector key_path_; + std::vector key_path_; bool pending_table_separator_ = false; TOML_API void print_pending_table_separator(); TOML_API - void print_key_segment(std::string_view); - - TOML_API - void print_key_path(); + void print(const key&); TOML_API void print_inline(const toml::table&); @@ -9432,8 +9730,8 @@ TOML_NAMESPACE_START continue; } - std::unique_ptr arr_storage = std::move(elems_[i]); - const auto leaf_count = arr->total_leaf_count(); + impl::node_ptr arr_storage = std::move(elems_[i]); + const auto leaf_count = arr->total_leaf_count(); if (leaf_count > 1u) preinsertion_resize(i + 1u, leaf_count - 1u); flatten_child(std::move(*arr), i); // increments i @@ -9483,8 +9781,8 @@ TOML_NAMESPACE_START : node(other), inline_{ other.inline_ } { - for (auto&& [k, v] : other) - map_.emplace_hint(map_.end(), k, impl::make_node(v)); + for (auto&& [k, v] : other.map_) + map_.emplace_hint(map_.end(), k, impl::make_node(*v)); #if TOML_LIFETIME_HOOKS TOML_TABLE_CREATED; @@ -9509,8 +9807,8 @@ TOML_NAMESPACE_START { node::operator=(rhs); map_.clear(); - for (auto&& [k, v] : rhs) - map_.emplace_hint(map_.end(), k, impl::make_node(v)); + for (auto&& [k, v] : rhs.map_) + map_.emplace_hint(map_.end(), k, impl::make_node(*v)); inline_ = rhs.inline_; } return *this; @@ -9537,9 +9835,9 @@ TOML_NAMESPACE_START if (ntype == node_type::none) ntype = map_.cbegin()->second->type(); - for (const auto& [k, v] : map_) + for (auto&& [k, v] : map_) { - (void)k; + static_cast(k); if (v->type() != ntype) return false; } @@ -9559,7 +9857,7 @@ TOML_NAMESPACE_START ntype = map_.cbegin()->second->type(); for (const auto& [k, v] : map_) { - (void)k; + static_cast(k); if (v->type() != ntype) { first_nonmatch = v.get(); @@ -9637,6 +9935,18 @@ TOML_NAMESPACE_START } return true; } + + TOML_EXTERNAL_LINKAGE + table::iterator table::lower_bound(std::string_view key) noexcept + { + return iterator{ map_.lower_bound(key) }; + } + + TOML_EXTERNAL_LINKAGE + table::const_iterator table::lower_bound(std::string_view key) const noexcept + { + return const_iterator{ map_.lower_bound(key) }; + } } TOML_NAMESPACE_END; @@ -10530,101 +10840,49 @@ TOML_ANON_NAMESPACE_START #define push_parse_scope_1(scope, line) push_parse_scope_2(scope, line) #define push_parse_scope(scope) push_parse_scope_1(scope, __LINE__) - // Q: "why not std::unique_ptr?? - // A: It caused a lot of bloat on some implementations so this exists an internal substitute. - class node_ptr - { - private: - node* node_ = {}; - - public: - TOML_NODISCARD_CTOR - node_ptr() noexcept = default; - - TOML_NODISCARD_CTOR - explicit node_ptr(node* n) noexcept // - : node_{ n } - {} - - ~node_ptr() noexcept - { - delete node_; - } - - node_ptr& operator=(node* val) noexcept - { - if (val != node_) - { - delete node_; - node_ = val; - } - return *this; - } - - node_ptr(const node_ptr&) = delete; - node_ptr& operator=(const node_ptr&) = delete; - node_ptr(node_ptr&&) = delete; - node_ptr& operator=(node_ptr&&) = delete; - - TOML_PURE_INLINE_GETTER - operator bool() const noexcept - { - return node_ != nullptr; - } - - TOML_PURE_INLINE_GETTER - node* operator->() noexcept - { - return node_; - } - - TOML_NODISCARD - node* release() noexcept - { - auto n = node_; - node_ = nullptr; - return n; - } - }; - struct parsed_key_buffer { std::string buffer; std::vector> segments; - source_region source; + std::vector starts; + std::vector ends; - void reset(source_position pos) noexcept + void clear() noexcept { buffer.clear(); segments.clear(); - source.begin = pos; + starts.clear(); + ends.clear(); } - void push_back(std::string_view segment) + void push_back(std::string_view segment, source_position b, source_position e) { segments.push_back({ buffer.length(), segment.length() }); buffer.append(segment); + starts.push_back(b); + ends.push_back(e); } - void finish(source_position pos) noexcept + TOML_PURE_INLINE_GETTER + std::string_view operator[](size_t i) const noexcept { - source.end = pos; + return std::string_view{ buffer.c_str() + segments[i].first, segments[i].second }; } TOML_PURE_INLINE_GETTER - std::string_view operator[](size_t i) noexcept + std::string_view back() const noexcept { - return std::string_view{ buffer.c_str() + segments[i].first, segments[i].second }; + return (*this)[segments.size() - 1u]; } TOML_PURE_INLINE_GETTER - std::string_view back() noexcept + bool empty() const noexcept { - return (*this)[segments.size() - 1u]; + return segments.empty(); } TOML_PURE_INLINE_GETTER - size_t size() noexcept + size_t size() const noexcept { return segments.size(); } @@ -11410,19 +11668,18 @@ TOML_IMPL_NAMESPACE_START assert_not_eof(); assert_or_assume(is_bare_key_character(*cp)); - auto& segment = string_buffer; - segment.clear(); + string_buffer.clear(); while (!is_eof()) { if (!is_bare_key_character(*cp)) break; - segment.append(cp->bytes, cp->count); + string_buffer.append(cp->bytes, cp->count); advance_and_return_if_error({}); } - return segment; + return string_buffer; } TOML_NODISCARD @@ -12146,13 +12403,13 @@ TOML_IMPL_NAMESPACE_START } TOML_NODISCARD - array* parse_array(); + node_ptr parse_array(); TOML_NODISCARD - table* parse_inline_table(); + node_ptr parse_inline_table(); TOML_NODISCARD - node* parse_value_known_prefixes() + node_ptr parse_value_known_prefixes() { return_if_error({}); assert_not_eof(); @@ -12170,31 +12427,31 @@ TOML_IMPL_NAMESPACE_START // floats beginning with '.' case U'.': - return new value{ parse_float() }; + return node_ptr{ new value{ parse_float() } }; // strings case U'"': [[fallthrough]]; case U'\'': - return new value{ parse_string().value }; + return node_ptr{ new value{ parse_string().value } }; // bools case U't': [[fallthrough]]; case U'f': [[fallthrough]]; case U'T': [[fallthrough]]; case U'F': - return new value{ parse_boolean() }; + return node_ptr{ new value{ parse_boolean() } }; // inf/nan case U'i': [[fallthrough]]; case U'I': [[fallthrough]]; case U'n': [[fallthrough]]; - case U'N': return new value{ parse_inf_or_nan() }; + case U'N': return node_ptr{ new value{ parse_inf_or_nan() } }; } return nullptr; } TOML_NODISCARD - node* parse_value() + node_ptr parse_value() { return_if_error({}); assert_not_eof(); @@ -12398,7 +12655,7 @@ TOML_IMPL_NAMESPACE_START { if (has_any(begins_zero | begins_digit)) { - val = new value{ static_cast(chars[0] - U'0') }; + val.reset(new value{ static_cast(chars[0] - U'0') }); advance(); // skip the digit break; } @@ -12417,7 +12674,7 @@ TOML_IMPL_NAMESPACE_START // typed parse functions to take over and show better diagnostics if there's an issue // (as opposed to the fallback "could not determine type" message) if (has_any(has_p)) - val = new value{ parse_hex_float() }; + val.reset(new value{ parse_hex_float() }); else if (has_any(has_x | has_o | has_b)) { int64_t i; @@ -12439,17 +12696,17 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - val = new value{ i }; + val.reset(new value{ i }); val->ref_cast().flags(flags); } else if (has_any(has_e) || (has_any(begins_zero | begins_digit) && chars[1] == U'.')) - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); else if (has_any(begins_sign)) { // single-digit signed integers if (char_count == 2u && has_any(has_digits)) { - val = new value{ static_cast(chars[1] - U'0') * (chars[0] == U'-' ? -1LL : 1LL) }; + val.reset(new value{ static_cast(chars[1] - U'0') * (chars[0] == U'-' ? -1LL : 1LL) }); advance(); // skip the sign advance(); // skip the digit break; @@ -12457,11 +12714,11 @@ TOML_IMPL_NAMESPACE_START // simple signed floats (e.g. +1.0) if (is_decimal_digit(chars[1]) && chars[2] == U'.') - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); // signed infinity or nan else if (is_match(chars[1], U'i', U'n', U'I', U'N')) - val = new value{ parse_inf_or_nan() }; + val.reset(new value{ parse_inf_or_nan() }); } return_if_error({}); @@ -12477,14 +12734,14 @@ TOML_IMPL_NAMESPACE_START // binary integers // 0b10 case bzero_msk | has_b: - val = new value{ parse_integer<2>() }; + val.reset(new value{ parse_integer<2>() }); val->ref_cast().flags(value_flags::format_as_binary); break; // octal integers // 0o10 case bzero_msk | has_o: - val = new value{ parse_integer<8>() }; + val.reset(new value{ parse_integer<8>() }); val->ref_cast().flags(value_flags::format_as_octal); break; @@ -12496,12 +12753,12 @@ TOML_IMPL_NAMESPACE_START case bzero_msk: [[fallthrough]]; case bdigit_msk: [[fallthrough]]; case begins_sign | has_digits | has_minus: [[fallthrough]]; - case begins_sign | has_digits | has_plus: val = new value{ parse_integer<10>() }; break; + case begins_sign | has_digits | has_plus: val.reset(new value{ parse_integer<10>() }); break; // hexadecimal integers // 0x10 case bzero_msk | has_x: - val = new value{ parse_integer<16>() }; + val.reset(new value{ parse_integer<16>() }); val->ref_cast().flags(value_flags::format_as_hexadecimal); break; @@ -12554,7 +12811,7 @@ TOML_IMPL_NAMESPACE_START case begins_sign | has_digits | has_e | signs_msk: [[fallthrough]]; case begins_sign | has_digits | has_dot | has_minus: [[fallthrough]]; case begins_sign | has_digits | has_dot | has_e | has_minus: - val = new value{ parse_float() }; + val.reset(new value{ parse_float() }); break; // hexadecimal floats @@ -12588,7 +12845,7 @@ TOML_IMPL_NAMESPACE_START case begins_sign | has_digits | has_x | has_dot | has_p | has_minus: [[fallthrough]]; case begins_sign | has_digits | has_x | has_dot | has_p | has_plus: [[fallthrough]]; case begins_sign | has_digits | has_x | has_dot | has_p | signs_msk: - val = new value{ parse_hex_float() }; + val.reset(new value{ parse_hex_float() }); break; // times @@ -12598,12 +12855,12 @@ TOML_IMPL_NAMESPACE_START case bzero_msk | has_colon: [[fallthrough]]; case bzero_msk | has_colon | has_dot: [[fallthrough]]; case bdigit_msk | has_colon: [[fallthrough]]; - case bdigit_msk | has_colon | has_dot: val = new value{ parse_time() }; break; + case bdigit_msk | has_colon | has_dot: val.reset(new value{ parse_time() }); break; // local dates // YYYY-MM-DD case bzero_msk | has_minus: [[fallthrough]]; - case bdigit_msk | has_minus: val = new value{ parse_date() }; break; + case bdigit_msk | has_minus: val.reset(new value{ parse_date() }); break; // date-times // YYYY-MM-DDTHH:MM @@ -12642,7 +12899,7 @@ TOML_IMPL_NAMESPACE_START case bzero_msk | has_minus | has_colon | has_dot | has_z | has_t: [[fallthrough]]; case bdigit_msk | has_minus | has_colon | has_z | has_t: [[fallthrough]]; case bdigit_msk | has_minus | has_colon | has_dot | has_z | has_t: - val = new value{ parse_date_time() }; + val.reset(new value{ parse_date_time() }); break; } } @@ -12666,7 +12923,7 @@ TOML_IMPL_NAMESPACE_START #endif val->source_ = { begin_pos, current_position(1), reader.source_path() }; - return val.release(); + return val; } bool parse_key() @@ -12676,9 +12933,8 @@ TOML_IMPL_NAMESPACE_START assert_or_assume(is_bare_key_character(*cp) || is_string_delimiter(*cp)); push_parse_scope("key"sv); - key_buffer.reset(current_position()); + key_buffer.clear(); recording_whitespace = false; - std::string_view pending_key_segment; while (!is_error()) { @@ -12687,9 +12943,12 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("bare keys may not begin with unicode combining marks"sv); #endif + std::string_view key_segment; + const auto key_begin = current_position(); + // bare_key_segment if (is_bare_key_character(*cp)) - pending_key_segment = parse_bare_key_segment(); + key_segment = parse_bare_key_segment(); // "quoted key segment" else if (is_string_delimiter(*cp)) @@ -12705,12 +12964,12 @@ TOML_IMPL_NAMESPACE_START { set_error_at(begin_pos, "multi-line strings are prohibited in "sv, - key_buffer.segments.empty() ? ""sv : "dotted "sv, + key_buffer.empty() ? ""sv : "dotted "sv, "keys"sv); return_after_error({}); } else - pending_key_segment = str.value; + key_segment = str.value; } // ??? @@ -12719,11 +12978,13 @@ TOML_IMPL_NAMESPACE_START to_sv(*cp), "'"sv); + const auto key_end = current_position(); + // whitespace following the key segment consume_leading_whitespace(); // store segment - key_buffer.push_back(pending_key_segment); + key_buffer.push_back(key_segment, key_begin, key_end); // eof or no more key to come if (is_eof() || *cp != U'.') @@ -12736,10 +12997,20 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - key_buffer.finish(current_position()); return true; } + TOML_NODISCARD + key make_key(size_t segment_index) const + { + TOML_ASSERT(key_buffer.size() > segment_index); + + return key{ + key_buffer[segment_index], + source_region{ key_buffer.starts[segment_index], key_buffer.ends[segment_index], root.source().path } + }; + } + TOML_NODISCARD table* parse_table_header() { @@ -12808,108 +13079,90 @@ TOML_IMPL_NAMESPACE_START if (!is_eof() && !consume_comment() && !consume_line_break()) set_error_and_return_default("expected a comment or whitespace, saw '"sv, to_sv(cp), "'"sv); } - TOML_ASSERT(!key_buffer.segments.empty()); + TOML_ASSERT(!key_buffer.empty()); // check if each parent is a table/table array, or can be created implicitly as a table. - auto parent = &root; + table* parent = &root; for (size_t i = 0, e = key_buffer.size() - 1u; i < e; i++) { - const auto segment = key_buffer[i]; - auto child = parent->get(segment); - if (!child) - { - child = parent->map_.emplace(segment, new table{}).first->second.get(); - implicit_tables.push_back(&child->ref_cast
()); - child->source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - parent = &child->ref_cast
(); - } - else if (child->is_table()) - { - parent = &child->ref_cast
(); - } - else if (child->is_array() - && impl::find(table_arrays.begin(), table_arrays.end(), &child->ref_cast())) - { - // table arrays are a special case; - // the spec dictates we select the most recently declared element in the array. - TOML_ASSERT(!child->ref_cast().elems_.empty()); - TOML_ASSERT(child->ref_cast().elems_.back()->is_table()); - parent = &child->ref_cast().back().ref_cast
(); - } - else - { - if (!is_arr && child->type() == node_type::table) - set_error_and_return_default("cannot redefine existing table '"sv, - to_sv(recording_buffer), - "'"sv); - else - set_error_and_return_default("cannot redefine existing "sv, - to_sv(child->type()), - " '"sv, - to_sv(recording_buffer), - "' as "sv, - is_arr ? "array-of-tables"sv : "table"sv); - } - } + const std::string_view segment = key_buffer[i]; + auto pit = parent->lower_bound(segment); - // check the last parent table for a node matching the last key. - // if there was no matching node, then sweet; - // we can freely instantiate a new table/table array. - const auto last_segment = key_buffer.back(); - auto matching_node = parent->get(last_segment); - if (!matching_node) - { - // if it's an array we need to make the array and it's first table element, - // set the starting regions, and return the table element - if (is_arr) + // parent already existed + if (pit != parent->end() && pit->first == segment) { - array& tbl_arr = parent->emplace(last_segment).first->second.ref_cast(); - table_arrays.push_back(&tbl_arr); - tbl_arr.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + node& p = pit->second; - table& tbl = tbl_arr.emplace_back
(); - tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - return &tbl; + if (auto tbl = p.as_table()) + parent = tbl; + else if (auto arr = p.as_array(); arr && impl::find(table_arrays.begin(), table_arrays.end(), arr)) + { + // table arrays are a special case; + // the spec dictates we select the most recently declared element in the array. + TOML_ASSERT(!arr->elems_.empty()); + TOML_ASSERT(arr->elems_.back()->is_table()); + parent = &arr->back().ref_cast
(); + } + else + { + if (!is_arr && p.type() == node_type::table) + set_error_and_return_default("cannot redefine existing table '"sv, + to_sv(recording_buffer), + "'"sv); + else + set_error_and_return_default("cannot redefine existing "sv, + to_sv(p.type()), + " '"sv, + to_sv(recording_buffer), + "' as "sv, + is_arr ? "array-of-tables"sv : "table"sv); + } } - // otherwise we're just making a table + // need to create a new implicit table else { - table& tbl = parent->emplace
(last_segment).first->second.ref_cast
(); - tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; - return &tbl; + pit = parent->emplace_hint
(pit, make_key(i)); + table& p = pit->second.ref_cast
(); + p.source_ = pit->first.source(); + + implicit_tables.push_back(&p); + parent = &p; } } + const auto last_segment = key_buffer.back(); + auto it = parent->lower_bound(last_segment); + // 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. - else + if (it != parent->end() && it->first == last_segment) { - if (is_arr && matching_node->is_array() - && impl::find(table_arrays.begin(), table_arrays.end(), &matching_node->ref_cast())) + node& matching_node = it->second; + if (auto arr = matching_node.as_array(); + is_arr && arr && impl::find(table_arrays.begin(), table_arrays.end(), arr)) { - table& tbl = matching_node->ref_cast().emplace_back
(); + table& tbl = arr->emplace_back
(); tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; return &tbl; } - else if (!is_arr && matching_node->is_table() && !implicit_tables.empty()) + else if (auto tbl = matching_node.as_table(); !is_arr && tbl && !implicit_tables.empty()) { - table& tbl = matching_node->ref_cast
(); - if (auto found = impl::find(implicit_tables.begin(), implicit_tables.end(), &tbl); - found && (tbl.empty() || tbl.is_homogeneous
())) + if (auto found = impl::find(implicit_tables.begin(), implicit_tables.end(), tbl); + found && (tbl->empty() || tbl->is_homogeneous
())) { implicit_tables.erase(implicit_tables.cbegin() + (found - implicit_tables.data())); - tbl.source_.begin = header_begin_pos; - tbl.source_.end = header_end_pos; - return &tbl; + tbl->source_.begin = header_begin_pos; + tbl->source_.end = header_end_pos; + return tbl; } } // if we get here it's a redefinition error. - if (!is_arr && matching_node->type() == node_type::table) + if (!is_arr && matching_node.type() == node_type::table) { set_error_at(header_begin_pos, "cannot redefine existing table '"sv, @@ -12921,7 +13174,7 @@ TOML_IMPL_NAMESPACE_START { set_error_at(header_begin_pos, "cannot redefine existing "sv, - to_sv(matching_node->type()), + to_sv(matching_node.type()), " '"sv, to_sv(recording_buffer), "' as "sv, @@ -12929,6 +13182,35 @@ TOML_IMPL_NAMESPACE_START return_after_error({}); } } + + // there was no matching node, sweet - we can freely instantiate a new table/table array. + else + { + auto last_key = make_key(key_buffer.size() - 1u); + + // if it's an array we need to make the array and it's first table element, + // set the starting regions, and return the table element + if (is_arr) + { + it = parent->emplace_hint(it, std::move(last_key)); + array& tbl_arr = it->second.ref_cast(); + table_arrays.push_back(&tbl_arr); + tbl_arr.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + + table& tbl = tbl_arr.emplace_back
(); + tbl.source_ = { header_begin_pos, header_end_pos, reader.source_path() }; + return &tbl; + } + + // otherwise we're just making a table + else + { + 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() }; + return &tbl; + } + } } bool parse_key_value_pair_and_insert(table* tbl) @@ -12963,58 +13245,67 @@ TOML_IMPL_NAMESPACE_START if (is_value_terminator(*cp)) set_error_and_return_default("expected value, saw '"sv, to_sv(*cp), "'"sv); - // if it's a dotted kvp we need to spawn the sub-tables if necessary, + // if it's a dotted kvp we need to spawn the parent sub-tables if necessary, // and set the target table to the second-to-last one in the chain if (key_buffer.size() > 1u) { for (size_t i = 0; i < key_buffer.size() - 1u; i++) { - const auto segment = key_buffer[i]; - auto child = tbl->get(segment); - if (!child) + const std::string_view segment = key_buffer[i]; + auto pit = tbl->lower_bound(segment); + + // parent already existed + if (pit != tbl->end() && pit->first == segment) { - child = tbl->map_.emplace(std::string{ segment }, new table{}).first->second.get(); - dotted_key_tables.push_back(&child->ref_cast
()); - child->source_ = key_buffer.source; + table* p = pit->second.as_table(); + if (!p + || !(impl::find(dotted_key_tables.begin(), dotted_key_tables.end(), p) + || impl::find(implicit_tables.begin(), implicit_tables.end(), p))) + { + set_error_at(key_buffer.starts[i], + "cannot redefine existing "sv, + to_sv(p->type()), + " as dotted key-value pair"sv); + return_after_error({}); + } + + tbl = p; } - else if (!child->is_table() - || !(impl::find(dotted_key_tables.begin(), - dotted_key_tables.end(), - &child->ref_cast
()) - || impl::find(implicit_tables.begin(), - implicit_tables.end(), - &child->ref_cast
()))) + + // need to create a new implicit table + else { - set_error_at(key_buffer.source.begin, - "cannot redefine existing "sv, - to_sv(child->type()), - " as dotted key-value pair"sv); - return_after_error({}); - } + pit = tbl->emplace_hint
(pit, make_key(i)); + table& p = pit->second.ref_cast
(); + p.source_ = pit->first.source(); - tbl = &child->ref_cast
(); + dotted_key_tables.push_back(&p); + tbl = &p; + } } } // ensure this isn't a redefinition - if (auto conflicting_node = tbl->get(key_buffer.back())) + const std::string_view last_segment = key_buffer.back(); + auto it = tbl->lower_bound(last_segment); + if (it != tbl->end() && it->first == last_segment) { set_error("cannot redefine existing "sv, - to_sv(conflicting_node->type()), + to_sv(it->second.type()), " '"sv, to_sv(recording_buffer), "'"sv); return_after_error({}); } - // cache the last segment since it might get overwritten by nested tables etc. - auto last_segment = std::string{ key_buffer.back() }; + // create the key first since the key buffer will likely get overritten during value parsing (inline tables) + auto last_key = make_key(key_buffer.size() - 1u); // now we can actually parse the value - auto val = node_ptr{ parse_value() }; + node_ptr val = parse_value(); return_if_error({}); - tbl->map_.emplace(std::move(last_segment), std::unique_ptr{ val.release() }); + tbl->map_.emplace_hint(it.raw_, std::move(last_key), std::move(val)); return true; } @@ -13107,8 +13398,7 @@ TOML_IMPL_NAMESPACE_START parser(utf8_reader_interface&& reader_) // : reader{ reader_ } { - root.source_ = { prev_pos, prev_pos, reader.source_path() }; - key_buffer.source.path = reader.source_path(); + root.source_ = { prev_pos, prev_pos, reader.source_path() }; if (!reader.peek_eof()) { @@ -13148,7 +13438,7 @@ TOML_IMPL_NAMESPACE_START }; TOML_EXTERNAL_LINKAGE - array* parser::parse_array() + node_ptr parser::parse_array() { return_if_error({}); assert_not_eof(); @@ -13209,11 +13499,11 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - return reinterpret_cast(arr_ptr.release()); + return arr_ptr; } TOML_EXTERNAL_LINKAGE - table* parser::parse_inline_table() + node_ptr parser::parse_inline_table() { return_if_error({}); assert_not_eof(); @@ -13293,7 +13583,7 @@ TOML_IMPL_NAMESPACE_START } return_if_error({}); - return reinterpret_cast(tbl_ptr.release()); + return tbl_ptr; } TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS @@ -13934,20 +14224,9 @@ TOML_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void toml_formatter::print_key_segment(std::string_view str) + void toml_formatter::print(const key& k) { - print_string(str, false, true); - } - - TOML_EXTERNAL_LINKAGE - void toml_formatter::print_key_path() - { - for (const auto& segment : key_path_) - { - if (std::addressof(segment) > key_path_.data()) - print_unformatted('.'); - print_key_segment(segment); - } + print_string(k.str(), false, true); } TOML_EXTERNAL_LINKAGE @@ -13968,7 +14247,7 @@ TOML_NAMESPACE_START print_unformatted(", "sv); first = true; - print_key_segment(k); + print(k); print_unformatted(" = "sv); const auto type = v.type(); @@ -14068,7 +14347,7 @@ TOML_NAMESPACE_START pending_table_separator_ = true; print_newline(); print_indent(); - print_key_segment(k); + print(k); print_unformatted(" = "sv); TOML_ASSUME(type != node_type::none); switch (type) @@ -14079,6 +14358,17 @@ TOML_NAMESPACE_START } } + const auto print_key_path = [&]() + { + size_t i{}; + for (const auto k : key_path_) + { + if (i++) + print_unformatted('.'); + print(*k); + } + }; + // non-inline tables for (auto&& [k, v] : tbl) { @@ -14120,7 +14410,7 @@ TOML_NAMESPACE_START if (child_value_count == 0u && (child_table_count > 0u || child_table_array_count > 0u)) skip_self = true; - key_path_.push_back(std::string_view{ k }); + key_path_.push_back(&k); if (!skip_self) { @@ -14150,7 +14440,7 @@ TOML_NAMESPACE_START if (indent_sub_tables()) increase_indent(); - key_path_.push_back(std::string_view{ k }); + key_path_.push_back(&k); for (size_t i = 0; i < arr.size(); i++) { @@ -14242,7 +14532,7 @@ TOML_NAMESPACE_START print_newline(true); print_indent(); - print_string(k, false); + print_string(k.str(), false); print_unformatted(" : "sv); const auto type = v.type(); @@ -14398,7 +14688,7 @@ TOML_NAMESPACE_START } parent_is_array = false; - print_string(k, false, true); + print_string(k.str(), false, true); print_unformatted(": "sv); const auto type = v.type();