From b11f28af788bc22824425c239249b3db12abdb96 Mon Sep 17 00:00:00 2001 From: Mark Gillard Date: Sat, 16 Jan 2021 12:59:10 +0200 Subject: [PATCH] fixed dotted kvps being unable to add subtables (fixes #61) also: - fixed extremely weird linker error on linux ICC (fixes #83) - added some missing GNU attributes - added additional tests --- docs/Doxyfile | 2 +- include/toml++/toml.h | 2 +- include/toml++/toml_array.h | 6 + include/toml++/toml_node.h | 30 +- include/toml++/toml_parser.hpp | 8 +- include/toml++/toml_preprocessor.h | 8 +- include/toml++/toml_table.h | 3 + include/toml++/toml_utf8_streams.h | 2 +- include/toml++/toml_version.h | 2 +- meson.build | 2 +- tests/parsing_tables.cpp | 490 +++++++++++++++++++---------- toml.hpp | 62 +++- 12 files changed, 421 insertions(+), 196 deletions(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 3d02246..cb6296f 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -348,7 +348,7 @@ PREDEFINED = \ "TOML_EXTERNAL_LINKAGE=" \ "TOML_TRIVIAL_ABI=" \ "TOML_EMPTY_BASES=" \ - "TOML_INTERFACE" \ + "TOML_ABSTRACT_BASE" \ "TOML_INTERNAL_LINKAGE=static" EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = NO diff --git a/include/toml++/toml.h b/include/toml++/toml.h index 6b87d01..2af0678 100644 --- a/include/toml++/toml.h +++ b/include/toml++/toml.h @@ -58,6 +58,7 @@ TOML_POP_WARNINGS // TOML_DISABLE_SPAM_WARNINGS #undef TOML_ABI_NAMESPACE_BOOL #undef TOML_ABI_NAMESPACE_END #undef TOML_ABI_NAMESPACE_START + #undef TOML_ABSTRACT_BASE #undef TOML_ALWAYS_INLINE #undef TOML_ANON_NAMESPACE #undef TOML_ANON_NAMESPACE_END @@ -100,7 +101,6 @@ TOML_POP_WARNINGS // TOML_DISABLE_SPAM_WARNINGS #undef TOML_IMPL_NAMESPACE_START #undef TOML_INT128 #undef TOML_INTELLISENSE - #undef TOML_INTERFACE #undef TOML_INTERNAL_LINKAGE #undef TOML_INT_CHARCONV #undef TOML_LANG_AT_LEAST diff --git a/include/toml++/toml_array.h b/include/toml++/toml_array.h index 71dcb14..40af08b 100644 --- a/include/toml++/toml_array.h +++ b/include/toml++/toml_array.h @@ -22,6 +22,7 @@ TOML_IMPL_NAMESPACE_START mutable raw_iterator raw_; + TOML_NODISCARD_CTOR array_iterator(raw_mutable_iterator raw) noexcept : raw_{ raw } {} @@ -40,8 +41,12 @@ TOML_IMPL_NAMESPACE_START using difference_type = ptrdiff_t; using iterator_category = typename std::iterator_traits::iterator_category; + TOML_NODISCARD_CTOR array_iterator() noexcept = default; + + TOML_NODISCARD_CTOR array_iterator(const array_iterator&) noexcept = default; + array_iterator& operator = (const array_iterator&) noexcept = default; array_iterator& operator++() noexcept // ++pre @@ -429,6 +434,7 @@ TOML_NAMESPACE_START [[nodiscard]] bool is_homogeneous(node_type ntype) const noexcept override; [[nodiscard]] bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept override; [[nodiscard]] bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept override; + template [[nodiscard]] bool is_homogeneous() const noexcept diff --git a/include/toml++/toml_node.h b/include/toml++/toml_node.h index 3f46a9d..c5627bc 100644 --- a/include/toml++/toml_node.h +++ b/include/toml++/toml_node.h @@ -61,7 +61,7 @@ TOML_NAMESPACE_START /// /// \detail A parsed TOML document forms a tree made up of tables, arrays and values. /// This type is the base of each of those, providing a lot of the polymorphic plumbing. - class TOML_INTERFACE TOML_API node + class TOML_ABSTRACT_BASE TOML_API node { private: friend class TOML_PARSER_TYPENAME; @@ -78,6 +78,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) impl::wrap_node& ref_cast() & noexcept { return *reinterpret_cast*>(this); @@ -86,6 +87,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) impl::wrap_node&& ref_cast() && noexcept { return std::move(*reinterpret_cast*>(this)); @@ -94,6 +96,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) const impl::wrap_node& ref_cast() const & noexcept { return *reinterpret_cast*>(this); @@ -106,9 +109,26 @@ TOML_NAMESPACE_START virtual ~node() noexcept = default; + + + #if defined(DOXYGEN) || !TOML_ICC || TOML_ICC_CL + /// \brief Returns the node's type identifier. [[nodiscard]] virtual node_type type() const noexcept = 0; + #else + + [[nodiscard]] virtual node_type type() const noexcept + { + // Q: "what the fuck?" + // A: https://github.com/marzer/tomlplusplus/issues/83 + // tl,dr: go home ICC, you're drunk. + + return type(); + } + + #endif + /// \brief Returns true if this node is a table. [[nodiscard]] virtual bool is_table() const noexcept = 0; /// \brief Returns true if this node is an array. @@ -142,6 +162,7 @@ TOML_NAMESPACE_START /// \returns Returns true if this node is an instance of the specified type. template [[nodiscard]] + TOML_ATTR(pure) bool is() const noexcept { using type = impl::unwrap_node; @@ -278,6 +299,7 @@ TOML_NAMESPACE_START /// \remarks Always returns `false` for empty tables and arrays. template [[nodiscard]] + TOML_ATTR(pure) bool is_homogeneous() const noexcept { using type = impl::unwrap_node; @@ -501,6 +523,7 @@ TOML_NAMESPACE_START /// \returns A pointer to the node as the given type, or nullptr if it was a different type. template [[nodiscard]] + TOML_ATTR(pure) impl::wrap_node* as() noexcept { using type = impl::unwrap_node; @@ -524,6 +547,7 @@ TOML_NAMESPACE_START /// \brief Gets a pointer to the node as a more specific node type (const overload). template [[nodiscard]] + TOML_ATTR(pure) const impl::wrap_node* as() const noexcept { using type = impl::unwrap_node; @@ -702,6 +726,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) static decltype(auto) do_ref(N&& n) noexcept { using type = impl::unwrap_node; @@ -794,6 +819,7 @@ TOML_NAMESPACE_START /// \returns A reference to the underlying data. template [[nodiscard]] + TOML_ATTR(pure) impl::unwrap_node& ref() & noexcept { return do_ref(*this); @@ -802,6 +828,7 @@ TOML_NAMESPACE_START /// \brief Gets a raw reference to a value node's underlying data (rvalue overload). template [[nodiscard]] + TOML_ATTR(pure) impl::unwrap_node&& ref() && noexcept { return do_ref(std::move(*this)); @@ -810,6 +837,7 @@ TOML_NAMESPACE_START /// \brief Gets a raw reference to a value node's underlying data (const lvalue overload). template [[nodiscard]] + TOML_ATTR(pure) const impl::unwrap_node& ref() const& noexcept { return do_ref(*this); diff --git a/include/toml++/toml_parser.hpp b/include/toml++/toml_parser.hpp index 375e2fc..79f7d47 100644 --- a/include/toml++/toml_parser.hpp +++ b/include/toml++/toml_parser.hpp @@ -2659,6 +2659,9 @@ TOML_IMPL_NAMESPACE_START return_if_error(); TOML_ASSERT(kvp.key.segments.size() >= 1_sz); + + // if it's a dotted kvp we need to spawn the sub-tables if necessary, + // and set the target table to the second-to-last one in the chain if (kvp.key.segments.size() > 1_sz) { for (size_t i = 0; i < kvp.key.segments.size() - 1_sz; i++) @@ -2671,13 +2674,14 @@ TOML_IMPL_NAMESPACE_START new toml::table{} ).first->second.get(); dotted_key_tables.push_back(&child->ref_cast()); - dotted_key_tables.back()->inline_ = true; child->source_ = kvp.value.get()->source_; } - else if (!child->is_table() || !find(dotted_key_tables, &child->ref_cast
())) + else if (!child->is_table() + || !(find(dotted_key_tables, &child->ref_cast
()) || find(implicit_tables, &child->ref_cast
()))) set_error("cannot redefine existing "sv, to_sv(child->type()), " as dotted key-value pair"sv); else child->source_.end = kvp.value.get()->source_.end; + return_if_error(); tab = &child->ref_cast
(); } diff --git a/include/toml++/toml_preprocessor.h b/include/toml++/toml_preprocessor.h index 9d7fc92..901e122 100644 --- a/include/toml++/toml_preprocessor.h +++ b/include/toml++/toml_preprocessor.h @@ -73,7 +73,7 @@ #if defined(_MSC_VER) // msvc compat mode #ifdef __has_declspec_attribute #if __has_declspec_attribute(novtable) - #define TOML_INTERFACE __declspec(novtable) + #define TOML_ABSTRACT_BASE __declspec(novtable) #endif #if __has_declspec_attribute(empty_bases) #define TOML_EMPTY_BASES __declspec(empty_bases) @@ -125,7 +125,7 @@ #define TOML_NEVER_INLINE __declspec(noinline) #define TOML_ASSUME(cond) __assume(cond) #define TOML_UNREACHABLE __assume(0) - #define TOML_INTERFACE __declspec(novtable) + #define TOML_ABSTRACT_BASE __declspec(novtable) #define TOML_EMPTY_BASES __declspec(empty_bases) #endif // msvc @@ -374,8 +374,8 @@ is no longer necessary. #define TOML_ATTR(...) #endif -#ifndef TOML_INTERFACE - #define TOML_INTERFACE +#ifndef TOML_ABSTRACT_BASE + #define TOML_ABSTRACT_BASE #endif #ifndef TOML_EMPTY_BASES diff --git a/include/toml++/toml_table.h b/include/toml++/toml_table.h index ef80bfa..283bae2 100644 --- a/include/toml++/toml_table.h +++ b/include/toml++/toml_table.h @@ -47,6 +47,7 @@ TOML_IMPL_NAMESPACE_START return TOML_LAUNDER(reinterpret_cast(&proxy)); } + TOML_NODISCARD_CTOR table_iterator(raw_mutable_iterator raw) noexcept : raw_{ raw } {} @@ -59,8 +60,10 @@ TOML_IMPL_NAMESPACE_START public: + TOML_NODISCARD_CTOR table_iterator() noexcept = default; + TOML_NODISCARD_CTOR table_iterator(const table_iterator& other) noexcept : raw_{ other.raw_ } {} diff --git a/include/toml++/toml_utf8_streams.h b/include/toml++/toml_utf8_streams.h index a5389a7..831aa27 100644 --- a/include/toml++/toml_utf8_streams.h +++ b/include/toml++/toml_utf8_streams.h @@ -174,7 +174,7 @@ TOML_IMPL_NAMESPACE_START #define TOML_ERROR err.emplace #endif - struct TOML_INTERFACE utf8_reader_interface + struct TOML_ABSTRACT_BASE utf8_reader_interface { [[nodiscard]] virtual const source_path_ptr& source_path() const noexcept = 0; diff --git a/include/toml++/toml_version.h b/include/toml++/toml_version.h index 73432bc..710b2f4 100644 --- a/include/toml++/toml_version.h +++ b/include/toml++/toml_version.h @@ -7,7 +7,7 @@ #define TOML_LIB_MAJOR 2 #define TOML_LIB_MINOR 3 -#define TOML_LIB_PATCH 0 +#define TOML_LIB_PATCH 1 #define TOML_LANG_MAJOR 1 #define TOML_LANG_MINOR 0 diff --git a/meson.build b/meson.build index 9b440ac..2fb3de1 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'tomlplusplus', 'cpp', - version : '2.3.0', + version : '2.3.1', meson_version : '>=0.53.0', license : 'MIT', default_options : [ # https://mesonbuild.com/Builtin-options.html diff --git a/tests/parsing_tables.cpp b/tests/parsing_tables.cpp index 06fb2b6..59007a4 100644 --- a/tests/parsing_tables.cpp +++ b/tests/parsing_tables.cpp @@ -7,219 +7,364 @@ TEST_CASE("parsing - tables") { + // these are the examples from https://toml.io/en/v1.0.0#table + + // "Tables are defined by headers, with square brackets on a line by themselves." + parsing_should_succeed( + FILE_LINE_ARGS, "[table]"sv, + [](table&& tbl) + { + REQUIRE(tbl["table"].as_table()); + CHECK(tbl["table"].as_table()->empty()); + CHECK(tbl["table"].as_table()->size() == 0u); + } + ); + parsing_should_fail(FILE_LINE_ARGS, "[]"sv); + + // "Under that, and until the next header or EOF, are the key/values of that table. + // Key/value pairs within tables are not guaranteed to be in any specific order." parsing_should_succeed( FILE_LINE_ARGS, R"( -[table] + [table-1] + key1 = "some string" + key2 = 123 -[table-1] -key1 = "some string" -key2 = 123 - -[table-2] -key1 = "another string" -key2 = 456 - -[dog."tater.man"] -type.name = "pug" - -[a.b.c] # this is best practice -[ d.e.f ] # same as [d.e.f] -[ g . h . i ] # same as [g.h.i] -[ j . "k" . 'l' ] # same as [j."k".'l'] - -# [x] you -# [x.y] don't -# [x.y.z] need these -[x.y.z.w] # for this to work - -[x] # defining a super-table afterwards is ok - -[fruit] -apple.color = "red" -apple.taste.sweet = true - -[fruit.apple.texture] # you can add sub-tables -smooth = true - -)"sv, + [table-2] + key1 = "another string" + key2 = 456 + )"sv, [](table&& tbl) { - REQUIRE(tbl["table"].as
()); - CHECK(tbl["table"].as
()->size() == 0u); - - REQUIRE(tbl["table-1"].as
()); - CHECK(tbl["table-1"].as
()->size() == 2u); + REQUIRE(tbl["table-1"].as_table()); + CHECK(tbl["table-1"].as_table()->size() == 2u); CHECK(tbl["table-1"]["key1"] == "some string"sv); CHECK(tbl["table-1"]["key2"] == 123); - REQUIRE(tbl["table-2"].as
()); - CHECK(tbl["table-2"].as
()->size() == 2u); + REQUIRE(tbl["table-2"].as_table()); + CHECK(tbl["table-2"].as_table()->size() == 2u); CHECK(tbl["table-2"]["key1"] == "another string"sv); CHECK(tbl["table-2"]["key2"] == 456); + } + ); - REQUIRE(tbl["dog"].as
()); - CHECK(tbl["dog"].as
()->size() == 1u); + // "Naming rules for tables are the same as for keys." (i.e. can be quoted) + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + [dog."tater.man"] + type.name = "pug" + )"sv, + [](table&& tbl) + { + REQUIRE(tbl["dog"].as_table()); + CHECK(tbl["dog"].as_table()->size() == 1u); - REQUIRE(tbl["dog"]["tater.man"].as
()); - CHECK(tbl["dog"]["tater.man"].as
()->size() == 1u); + REQUIRE(tbl["dog"]["tater.man"].as_table()); + CHECK(tbl["dog"]["tater.man"].as_table()->size() == 1u); CHECK(tbl["dog"]["tater.man"]["type"]["name"] == "pug"sv); - - CHECK(tbl["a"].as
()); - CHECK(tbl["a"]["b"].as
()); - CHECK(tbl["a"]["b"]["c"].as
()); - - CHECK(tbl["d"].as
()); - CHECK(tbl["d"]["e"].as
()); - CHECK(tbl["d"]["e"]["f"].as
()); - - CHECK(tbl["g"].as
()); - CHECK(tbl["g"]["h"].as
()); - CHECK(tbl["g"]["h"]["i"].as
()); - - CHECK(tbl["j"].as
()); - CHECK(tbl["j"]["k"].as
()); - CHECK(tbl["j"]["k"]["l"].as
()); - - REQUIRE(tbl["fruit"].as
()); - CHECK(tbl["fruit"]["apple"]["color"] == "red"sv); - CHECK(tbl["fruit"]["apple"]["taste"]["sweet"] == true); - CHECK(tbl["fruit"]["apple"]["texture"]["smooth"] == true); } ); - - parsing_should_fail(FILE_LINE_ARGS, R"( -# DO NOT DO THIS - -[fruit] -apple = "red" - -[fruit] -orange = "orange" -)"sv); - - parsing_should_fail(FILE_LINE_ARGS, R"( -# DO NOT DO THIS EITHER - -[fruit] -apple = "red" - -[fruit.apple] -texture = "smooth" -)"sv); - - parsing_should_fail(FILE_LINE_ARGS, R"( -[fruit] -apple.color = "red" -apple.taste.sweet = true - -[fruit.apple] -)"sv); - - parsing_should_fail(FILE_LINE_ARGS, R"( -[fruit] -apple.color = "red" -apple.taste.sweet = true - -[fruit.apple.taste] -)"sv); - + // "Whitespace around the key is ignored. However, best practice is to not use any extraneous whitespace." parsing_should_succeed( FILE_LINE_ARGS, R"( -# VALID BUT DISCOURAGED -[fruit.apple] -[animal] -[fruit.orange] -)"sv, + [a.b.c] # this is best practice + [ d.e.f ] # same as [d.e.f] + [ g . h . i ] # same as [g.h.i] + [ j . "k" . 'l' ] # same as [j."k".'l'] + )"sv, [](table&& tbl) { - REQUIRE(tbl["animal"].as
()); - CHECK(tbl["animal"].as
()->size() == 0u); + CHECK(tbl["a"].as_table()); + CHECK(tbl["a"]["b"].as_table()); + CHECK(tbl["a"]["b"]["c"].as_table()); - REQUIRE(tbl["fruit"].as
()); - CHECK(tbl["fruit"].as
()->size() == 2u); + CHECK(tbl["d"].as_table()); + CHECK(tbl["d"]["e"].as_table()); + CHECK(tbl["d"]["e"]["f"].as_table()); - REQUIRE(tbl["fruit"]["apple"].as
()); - REQUIRE(tbl["fruit"]["orange"].as
()); + CHECK(tbl["g"].as_table()); + CHECK(tbl["g"]["h"].as_table()); + CHECK(tbl["g"]["h"]["i"].as_table()); + + CHECK(tbl["j"].as_table()); + CHECK(tbl["j"]["k"].as_table()); + CHECK(tbl["j"]["k"]["l"].as_table()); } ); - + // "You don't need to specify all the super-tables if you don't want to. TOML knows how to do it for you." parsing_should_succeed( FILE_LINE_ARGS, R"( -# RECOMMENDED -[fruit.apple] -[fruit.orange] -[animal] -)"sv, + # [x] you + # [x.y] don't + # [x.y.z] need these + [x.y.z.w] # for this to work + + [x] # defining a super-table afterwards is ok + )"sv, [](table&& tbl) { - REQUIRE(tbl["animal"].as
()); - CHECK(tbl["animal"].as
()->size() == 0u); - - REQUIRE(tbl["fruit"].as
()); - CHECK(tbl["fruit"].as
()->size() == 2u); - - REQUIRE(tbl["fruit"]["apple"].as
()); - REQUIRE(tbl["fruit"]["orange"].as
()); + CHECK(tbl["x"].as_table()); + CHECK(tbl["x"]["y"].as_table()); + CHECK(tbl["x"]["y"]["z"].as_table()); + CHECK(tbl["x"]["y"]["z"]["w"].as_table()); } ); - parsing_should_fail(FILE_LINE_ARGS, R"([])"sv); + // "Like keys, you cannot define a table more than once. Doing so is invalid." + parsing_should_fail(FILE_LINE_ARGS, R"( + # DO NOT DO THIS + + [fruit] + apple = "red" + + [fruit] + orange = "orange" + )"sv); + parsing_should_fail(FILE_LINE_ARGS, R"( + # DO NOT DO THIS EITHER + + [fruit] + apple = "red" + + [fruit.apple] + texture = "smooth" + )"sv); + + // "Defining tables out-of-order is discouraged." + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + # VALID BUT DISCOURAGED + [fruit.apple] + [animal] + [fruit.orange] + )"sv, + [](table&& tbl) + { + CHECK(tbl["fruit"].as_table()); + CHECK(tbl["fruit"]["apple"].as_table()); + CHECK(tbl["animal"].as_table()); + CHECK(tbl["fruit"]["orange"].as_table()); + } + ); + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + # RECOMMENDED + [fruit.apple] + [fruit.orange] + [animal] + )"sv, + [](table&& tbl) + { + CHECK(tbl["fruit"].as_table()); + CHECK(tbl["fruit"]["apple"].as_table()); + CHECK(tbl["fruit"]["orange"].as_table()); + CHECK(tbl["animal"].as_table()); + } + ); + + // "The top-level table, also called the root table, starts at the beginning of the document + // and ends just before the first table header (or EOF)." + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + # Top-level table begins. + name = "Fido" + breed = "pug" + + # Top-level table ends. + [owner] + name = "Regina Dogman" + member_since = 1999-08-04 + )"sv, + [](table&& tbl) + { + CHECK(tbl["name"].as_string()); + CHECK(*tbl["name"].as_string() == "Fido"sv); + CHECK(tbl["breed"].as_string()); + CHECK(*tbl["breed"].as_string() == "pug"sv); + + CHECK(tbl["owner"].as_table()); + CHECK(*tbl["owner"]["name"].as_string() == "Regina Dogman"sv); + + static constexpr auto member_since = toml::date{ 1999, 8, 4 }; + CHECK(*tbl["owner"]["member_since"].as_date() == member_since); + } + ); + + // "Dotted keys create and define a table for each key part before the last one, + // provided that such tables were not previously created." + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + fruit.apple.color = "red" + # Defines a table named fruit + # Defines a table named fruit.apple + + fruit.apple.taste.sweet = true + # Defines a table named fruit.apple.taste + # fruit and fruit.apple were already created + )"sv, + [](table&& tbl) + { + CHECK(tbl["fruit"].as_table()); + CHECK(tbl["fruit"]["apple"].as_table()); + CHECK(tbl["fruit"]["apple"]["color"].as_string()); + CHECK(*tbl["fruit"]["apple"]["color"].as_string() == "red"sv); + + CHECK(tbl["fruit"]["apple"]["taste"].as_table()); + CHECK(tbl["fruit"]["apple"]["taste"]["sweet"].as_boolean()); + CHECK(*tbl["fruit"]["apple"]["taste"]["sweet"].as_boolean() == true); + } + ); + + // "Since tables cannot be defined more than once, redefining such tables using a [table] header is not allowed." + parsing_should_fail(FILE_LINE_ARGS, R"( + [fruit] + apple.color = "red" + apple.taste.sweet = true + + [fruit.apple] # INVALID + )"sv); + parsing_should_fail(FILE_LINE_ARGS, R"( + [fruit] + apple.color = "red" + apple.taste.sweet = true + + [fruit.apple.taste] # INVALID + )"sv); + + // "Likewise, using dotted keys to redefine tables already defined in [table] form is not allowed." + parsing_should_fail(FILE_LINE_ARGS, R"( + [fruit.apple.taste] + sweet = true + + [fruit] + apple.taste = { sweet = false } # INVALID + )"sv); + parsing_should_fail(FILE_LINE_ARGS, R"( + [fruit.apple.taste] + sweet = true + + [fruit] + apple.taste.foo = "bar" # INVALID + )"sv); + + // "The [table] form can, however, be used to define sub-tables within tables defined via dotted keys." + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + [fruit] + apple.color = "red" + apple.taste.sweet = true + + [fruit.apple.texture] # you can add sub-tables + smooth = true + )"sv, + [](table&& tbl) + { + CHECK(tbl["fruit"].as_table()); + CHECK(tbl["fruit"]["apple"].as_table()); + CHECK(tbl["fruit"]["apple"]["color"].as_string()); + CHECK(*tbl["fruit"]["apple"]["color"].as_string() == "red"sv); + + CHECK(tbl["fruit"]["apple"]["texture"].as_table()); + CHECK(tbl["fruit"]["apple"]["texture"]["smooth"].as_boolean()); + CHECK(*tbl["fruit"]["apple"]["texture"]["smooth"].as_boolean() == true); + } + ); + parsing_should_fail(FILE_LINE_ARGS, R"( + [fruit] + apple.color = "red" + apple.taste.sweet = true + + [fruit.apple] + shape = "round" + + [fruit.apple.texture] + smooth = true + )"sv); + + // same as above but the table order is reversed. + // see: https://github.com/toml-lang/toml/issues/769 + parsing_should_succeed( + FILE_LINE_ARGS, + R"( + [fruit.apple.texture] + smooth = true + + [fruit] + apple.color = "red" + apple.taste.sweet = true + )"sv, + [](table&& tbl) + { + CHECK(tbl["fruit"].as_table()); + CHECK(tbl["fruit"]["apple"].as_table()); + CHECK(tbl["fruit"]["apple"]["color"].as_string()); + CHECK(*tbl["fruit"]["apple"]["color"].as_string() == "red"sv); + + CHECK(tbl["fruit"]["apple"]["texture"].as_table()); + CHECK(tbl["fruit"]["apple"]["texture"]["smooth"].as_boolean()); + CHECK(*tbl["fruit"]["apple"]["texture"]["smooth"].as_boolean() == true); + } + ); } TEST_CASE("parsing - inline tables") { + // these are the examples from https://toml.io/en/v1.0.0#inline-table + parsing_should_succeed( FILE_LINE_ARGS, R"( -name = { first = "Tom", last = "Preston-Werner" } -point = { x = 1, y = 2 } -animal = { type.name = "pug" } - -[product] -type = { name = "Nail" } -)"sv, + name = { first = "Tom", last = "Preston-Werner" } + point = { x = 1, y = 2 } + animal = { type.name = "pug" } + )"sv, [](table&& tbl) { - REQUIRE(tbl["name"].as
()); - CHECK(tbl["name"].as
()->size() == 2u); + REQUIRE(tbl["name"].as_table()); + CHECK(tbl["name"].as_table()->size() == 2u); + CHECK(tbl["name"].as_table()->is_inline()); CHECK(tbl["name"]["first"] == "Tom"sv); CHECK(tbl["name"]["last"] == "Preston-Werner"sv); - REQUIRE(tbl["point"].as
()); - CHECK(tbl["point"].as
()->size() == 2u); + REQUIRE(tbl["point"].as_table()); + CHECK(tbl["point"].as_table()->size() == 2u); + CHECK(tbl["point"].as_table()->is_inline()); CHECK(tbl["point"]["x"] == 1); CHECK(tbl["point"]["y"] == 2); - REQUIRE(tbl["animal"].as
()); - CHECK(tbl["animal"].as
()->size() == 1u); - REQUIRE(tbl["animal"]["type"].as
()); - CHECK(tbl["animal"]["type"].as
()->size() == 1u); + REQUIRE(tbl["animal"].as_table()); + CHECK(tbl["animal"].as_table()->size() == 1u); + CHECK(tbl["animal"].as_table()->is_inline()); + REQUIRE(tbl["animal"]["type"].as_table()); + CHECK(tbl["animal"]["type"].as_table()->size() == 1u); CHECK(tbl["animal"]["type"]["name"] == "pug"sv); - - REQUIRE(tbl["product"].as
()); - CHECK(tbl["product"].as
()->size() == 1u); - REQUIRE(tbl["product"]["type"].as
()); - CHECK(tbl["product"]["type"].as
()->size() == 1u); - CHECK(tbl["product"]["type"]["name"] == "Nail"sv); } ); + // "Inline tables are fully self-contained and define all keys and sub-tables within them. + // Keys and sub-tables cannot be added outside the braces." parsing_should_fail(FILE_LINE_ARGS, R"( -[product] -type = { name = "Nail" } -type.edible = false # INVALID -)"sv); + [product] + type = { name = "Nail" } + type.edible = false # INVALID + )"sv); + // "Similarly, inline tables cannot be used to add keys or sub-tables to an already-defined table." parsing_should_fail(FILE_LINE_ARGS, R"( -[product] -type.name = "Nail" -type = { edible = false } # INVALID -)"sv); + [product] + type.name = "Nail" + type = { edible = false } # INVALID + )"sv); // "newlines are allowed between the curly braces [if] they are valid within a value." parsing_should_succeed( @@ -232,8 +377,8 @@ test = { val1 = "foo", val2 = [ )"sv, [](table&& tbl) { - REQUIRE(tbl["test"].as
()); - CHECK(tbl["test"].as
()->size() == 3u); + REQUIRE(tbl["test"].as_table()); + CHECK(tbl["test"].as_table()->size() == 3u); CHECK(tbl["test"]["val1"] == "foo"sv); REQUIRE(tbl["test"]["val2"].as()); CHECK(tbl["test"]["val2"].as()->size() == 3u); @@ -257,8 +402,8 @@ name = { )"sv, [](table&& tbl) { - REQUIRE(tbl["name"].as
()); - CHECK(tbl["name"].as
()->size() == 2u); + REQUIRE(tbl["name"].as_table()); + CHECK(tbl["name"].as_table()->size() == 2u); CHECK(tbl["name"]["first"] == "Tom"sv); CHECK(tbl["name"]["last"] == "Preston-Werner"sv); } @@ -345,16 +490,16 @@ color = "gray" CHECK(tbl["products"].as()->is_homogeneous()); CHECK(tbl["products"].as()->is_array_of_tables()); - REQUIRE(tbl["products"][0].as
()); - CHECK(tbl["products"][0].as
()->size() == 2u); + REQUIRE(tbl["products"][0].as_table()); + CHECK(tbl["products"][0].as_table()->size() == 2u); CHECK(tbl["products"][0]["name"] == "Hammer"sv); CHECK(tbl["products"][0]["sku"] == 738594937); - REQUIRE(tbl["products"][1].as
()); - CHECK(tbl["products"][1].as
()->size() == 0u); + REQUIRE(tbl["products"][1].as_table()); + CHECK(tbl["products"][1].as_table()->size() == 0u); - REQUIRE(tbl["products"][2].as
()); - CHECK(tbl["products"][2].as
()->size() == 3u); + REQUIRE(tbl["products"][2].as_table()); + CHECK(tbl["products"][2].as_table()->size() == 3u); CHECK(tbl["products"][2]["name"] == "Nail"sv); CHECK(tbl["products"][2]["sku"] == 284758393); CHECK(tbl["products"][2]["color"] == "gray"sv); @@ -365,12 +510,12 @@ color = "gray" CHECK(tbl["fruit"].as()->is_homogeneous()); CHECK(tbl["fruit"].as()->is_array_of_tables()); - REQUIRE(tbl["fruit"][0].as
()); - CHECK(tbl["fruit"][0].as
()->size() == 3u); + REQUIRE(tbl["fruit"][0].as_table()); + CHECK(tbl["fruit"][0].as_table()->size() == 3u); CHECK(tbl["fruit"][0]["name"] == "apple"sv); - REQUIRE(tbl["fruit"][0]["physical"].as
()); - CHECK(tbl["fruit"][0]["physical"].as
()->size() == 2u); + REQUIRE(tbl["fruit"][0]["physical"].as_table()); + CHECK(tbl["fruit"][0]["physical"].as_table()->size() == 2u); CHECK(tbl["fruit"][0]["physical"]["color"] == "red"sv); CHECK(tbl["fruit"][0]["physical"]["shape"] == "round"sv); @@ -381,8 +526,8 @@ color = "gray" CHECK(tbl["fruit"][0]["variety"][0]["name"] == "red delicious"sv); CHECK(tbl["fruit"][0]["variety"][1]["name"] == "granny smith"sv); - REQUIRE(tbl["fruit"][1].as
()); - CHECK(tbl["fruit"][1].as
()->size() == 2u); + REQUIRE(tbl["fruit"][1].as_table()); + CHECK(tbl["fruit"][1].as_table()->size() == 2u); CHECK(tbl["fruit"][1]["name"] == "banana"sv); REQUIRE(tbl["fruit"][1]["variety"].as()); @@ -437,5 +582,4 @@ fruit = [] [[fruit.physical]] color = "green" )"sv); - } diff --git a/toml.hpp b/toml.hpp index 10e4e8b..5d423cf 100644 --- a/toml.hpp +++ b/toml.hpp @@ -1,6 +1,6 @@ //---------------------------------------------------------------------------------------------------------------------- // -// toml++ v2.3.0 +// toml++ v2.3.1 // https://github.com/marzer/tomlplusplus // SPDX-License-Identifier: MIT // @@ -108,7 +108,7 @@ #if defined(_MSC_VER) // msvc compat mode #ifdef __has_declspec_attribute #if __has_declspec_attribute(novtable) - #define TOML_INTERFACE __declspec(novtable) + #define TOML_ABSTRACT_BASE __declspec(novtable) #endif #if __has_declspec_attribute(empty_bases) #define TOML_EMPTY_BASES __declspec(empty_bases) @@ -156,7 +156,7 @@ #define TOML_NEVER_INLINE __declspec(noinline) #define TOML_ASSUME(cond) __assume(cond) #define TOML_UNREACHABLE __assume(0) - #define TOML_INTERFACE __declspec(novtable) + #define TOML_ABSTRACT_BASE __declspec(novtable) #define TOML_EMPTY_BASES __declspec(empty_bases) #endif // msvc @@ -389,8 +389,8 @@ is no longer necessary. #define TOML_ATTR(...) #endif -#ifndef TOML_INTERFACE - #define TOML_INTERFACE +#ifndef TOML_ABSTRACT_BASE + #define TOML_ABSTRACT_BASE #endif #ifndef TOML_EMPTY_BASES @@ -527,7 +527,7 @@ is no longer necessary. #define TOML_LIB_MAJOR 2 #define TOML_LIB_MINOR 3 -#define TOML_LIB_PATCH 0 +#define TOML_LIB_PATCH 1 #define TOML_LANG_MAJOR 1 #define TOML_LANG_MINOR 0 @@ -2175,7 +2175,7 @@ TOML_POP_WARNINGS // TOML_DISABLE_SWITCH_WARNINGS TOML_NAMESPACE_START { - class TOML_INTERFACE TOML_API node + class TOML_ABSTRACT_BASE TOML_API node { private: friend class TOML_PARSER_TYPENAME; @@ -2192,6 +2192,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) impl::wrap_node& ref_cast() & noexcept { return *reinterpret_cast*>(this); @@ -2200,6 +2201,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) impl::wrap_node&& ref_cast() && noexcept { return std::move(*reinterpret_cast*>(this)); @@ -2208,6 +2210,7 @@ TOML_NAMESPACE_START template [[nodiscard]] TOML_ALWAYS_INLINE + TOML_ATTR(pure) const impl::wrap_node& ref_cast() const & noexcept { return *reinterpret_cast*>(this); @@ -2220,7 +2223,23 @@ TOML_NAMESPACE_START virtual ~node() noexcept = default; + #if defined(DOXYGEN) || !TOML_ICC || TOML_ICC_CL + [[nodiscard]] virtual node_type type() const noexcept = 0; + + #else + + [[nodiscard]] virtual node_type type() const noexcept + { + // Q: "what the fuck?" + // A: https://github.com/marzer/tomlplusplus/issues/83 + // tl,dr: go home ICC, you're drunk. + + return type(); + } + + #endif + [[nodiscard]] virtual bool is_table() const noexcept = 0; [[nodiscard]] virtual bool is_array() const noexcept = 0; [[nodiscard]] virtual bool is_value() const noexcept = 0; @@ -2236,6 +2255,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) bool is() const noexcept { using type = impl::unwrap_node; @@ -2280,6 +2300,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) bool is_homogeneous() const noexcept { using type = impl::unwrap_node; @@ -2326,6 +2347,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) impl::wrap_node* as() noexcept { using type = impl::unwrap_node; @@ -2348,6 +2370,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) const impl::wrap_node* as() const noexcept { using type = impl::unwrap_node; @@ -2520,6 +2543,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) static decltype(auto) do_ref(N&& n) noexcept { using type = impl::unwrap_node; @@ -2563,6 +2587,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) impl::unwrap_node& ref() & noexcept { return do_ref(*this); @@ -2570,6 +2595,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) impl::unwrap_node&& ref() && noexcept { return do_ref(std::move(*this)); @@ -2577,6 +2603,7 @@ TOML_NAMESPACE_START template [[nodiscard]] + TOML_ATTR(pure) const impl::unwrap_node& ref() const& noexcept { return do_ref(*this); @@ -3430,6 +3457,7 @@ TOML_IMPL_NAMESPACE_START mutable raw_iterator raw_; + TOML_NODISCARD_CTOR array_iterator(raw_mutable_iterator raw) noexcept : raw_{ raw } {} @@ -3448,8 +3476,12 @@ TOML_IMPL_NAMESPACE_START using difference_type = ptrdiff_t; using iterator_category = typename std::iterator_traits::iterator_category; + TOML_NODISCARD_CTOR array_iterator() noexcept = default; + + TOML_NODISCARD_CTOR array_iterator(const array_iterator&) noexcept = default; + array_iterator& operator = (const array_iterator&) noexcept = default; array_iterator& operator++() noexcept // ++pre @@ -3738,6 +3770,7 @@ TOML_NAMESPACE_START [[nodiscard]] bool is_homogeneous(node_type ntype) const noexcept override; [[nodiscard]] bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept override; [[nodiscard]] bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept override; + template [[nodiscard]] bool is_homogeneous() const noexcept @@ -4039,6 +4072,7 @@ TOML_IMPL_NAMESPACE_START return TOML_LAUNDER(reinterpret_cast(&proxy)); } + TOML_NODISCARD_CTOR table_iterator(raw_mutable_iterator raw) noexcept : raw_{ raw } {} @@ -4051,8 +4085,10 @@ TOML_IMPL_NAMESPACE_START public: + TOML_NODISCARD_CTOR table_iterator() noexcept = default; + TOML_NODISCARD_CTOR table_iterator(const table_iterator& other) noexcept : raw_{ other.raw_ } {} @@ -6929,7 +6965,7 @@ TOML_IMPL_NAMESPACE_START #define TOML_ERROR err.emplace #endif - struct TOML_INTERFACE utf8_reader_interface + struct TOML_ABSTRACT_BASE utf8_reader_interface { [[nodiscard]] virtual const source_path_ptr& source_path() const noexcept = 0; @@ -11408,6 +11444,9 @@ TOML_IMPL_NAMESPACE_START return_if_error(); TOML_ASSERT(kvp.key.segments.size() >= 1_sz); + + // if it's a dotted kvp we need to spawn the sub-tables if necessary, + // and set the target table to the second-to-last one in the chain if (kvp.key.segments.size() > 1_sz) { for (size_t i = 0; i < kvp.key.segments.size() - 1_sz; i++) @@ -11420,13 +11459,14 @@ TOML_IMPL_NAMESPACE_START new toml::table{} ).first->second.get(); dotted_key_tables.push_back(&child->ref_cast
()); - dotted_key_tables.back()->inline_ = true; child->source_ = kvp.value.get()->source_; } - else if (!child->is_table() || !find(dotted_key_tables, &child->ref_cast
())) + else if (!child->is_table() + || !(find(dotted_key_tables, &child->ref_cast
()) || find(implicit_tables, &child->ref_cast
()))) set_error("cannot redefine existing "sv, to_sv(child->type()), " as dotted key-value pair"sv); else child->source_.end = kvp.value.get()->source_.end; + return_if_error(); tab = &child->ref_cast
(); } @@ -12021,6 +12061,7 @@ TOML_POP_WARNINGS // TOML_DISABLE_SPAM_WARNINGS #undef TOML_ABI_NAMESPACE_BOOL #undef TOML_ABI_NAMESPACE_END #undef TOML_ABI_NAMESPACE_START + #undef TOML_ABSTRACT_BASE #undef TOML_ALWAYS_INLINE #undef TOML_ANON_NAMESPACE #undef TOML_ANON_NAMESPACE_END @@ -12063,7 +12104,6 @@ TOML_POP_WARNINGS // TOML_DISABLE_SPAM_WARNINGS #undef TOML_IMPL_NAMESPACE_START #undef TOML_INT128 #undef TOML_INTELLISENSE - #undef TOML_INTERFACE #undef TOML_INTERNAL_LINKAGE #undef TOML_INT_CHARCONV #undef TOML_LANG_AT_LEAST