diff --git a/src/core/io/src/4C_io_input_spec.cpp b/src/core/io/src/4C_io_input_spec.cpp index 9068a5a88e..7ff85a79d2 100644 --- a/src/core/io/src/4C_io_input_spec.cpp +++ b/src/core/io/src/4C_io_input_spec.cpp @@ -18,7 +18,7 @@ Core::IO::InputSpec::InputSpec(std::unique_ptr pimpl) { } -void Core::IO::InputSpec::fully_parse( +void Core::IO::InputSpec::deprecated_parse( ValueParser& parser, Core::IO::InputParameterContainer& container) const { FOUR_C_ASSERT(pimpl_, "InputSpec is empty."); diff --git a/src/core/io/src/4C_io_input_spec.hpp b/src/core/io/src/4C_io_input_spec.hpp index 510b000795..d488fc489d 100644 --- a/src/core/io/src/4C_io_input_spec.hpp +++ b/src/core/io/src/4C_io_input_spec.hpp @@ -80,8 +80,10 @@ namespace Core::IO /** * Use the @p parser to parse whatever this InputSpec expects. The results are stored in the * @p container. If parsing fails, an exception is thrown. + * + * @deprecated This function is used to parse legacy dat-style strings. Use match() in new code. */ - void fully_parse(ValueParser& parser, InputParameterContainer& container) const; + void deprecated_parse(ValueParser& parser, InputParameterContainer& container) const; /** * Match the content in @p yaml to the expected input format of this InputSpec. If the content diff --git a/src/core/io/src/4C_io_meshreader.cpp b/src/core/io/src/4C_io_meshreader.cpp index cd385f96e5..f315330c3c 100644 --- a/src/core/io/src/4C_io_meshreader.cpp +++ b/src/core/io/src/4C_io_meshreader.cpp @@ -317,7 +317,7 @@ namespace const auto& linedef = ed.get(eletype, cell_info.cell_type); Core::IO::InputParameterContainer data; - linedef.fully_parse(parser, data); + linedef.deprecated_parse(parser, data); ele->set_node_ids_one_based_index(cell_info.nodal_ids); ele->read_element(eletype, Core::FE::string_to_cell_type(cell_info.cell_type_str), data, diff --git a/src/core/io/tests/4C_io_input_spec_legacy_test.cpp b/src/core/io/tests/4C_io_input_spec_legacy_test.cpp new file mode 100644 index 0000000000..5dccc3a471 --- /dev/null +++ b/src/core/io/tests/4C_io_input_spec_legacy_test.cpp @@ -0,0 +1,667 @@ +// This file is part of 4C multiphysics licensed under the +// GNU Lesser General Public License v3.0 or later. +// +// See the LICENSE.md file in the top-level for license information. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include + +#include "4C_io_input_spec_builders.hpp" +#include "4C_io_input_spec_builders.templates.hpp" +#include "4C_unittest_utils_assertions_test.hpp" + + +// Contains tests related to legacy dat-style input + +namespace +{ + using namespace FourC; + using namespace Core::IO; + using namespace Core::IO::InputSpecBuilders; + + TEST(InputSpecLegacyTest, Simple) + { + auto spec = all_of({ + parameter("a", {.description = "An integer", .default_value = 1}), + parameter("b"), + parameter("d"), + }); + InputParameterContainer container; + std::string stream("b 2.0 d true // trailing comment"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1); + EXPECT_EQ(container.get("b"), 2.0); + EXPECT_EQ(container.get("d"), true); + } + + TEST(InputSpecLegacyTest, OptionalLeftOut) + { + auto spec = all_of({ + parameter("a"), + parameter("b"), + parameter("c", {.default_value = "default"}), + }); + + { + InputParameterContainer container; + std::string stream("a 1 b 2.0 // c 1"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1); + EXPECT_EQ(container.get("b"), 2.0); + EXPECT_EQ(container.get("c"), "default"); + } + } + + TEST(InputSpecLegacyTest, RequiredLeftOut) + { + auto spec = all_of({ + parameter("a"), + parameter("b"), + parameter("c"), + }); + + { + InputParameterContainer container; + std::string stream("a 1 b 2.0"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Required value 'c' not found in input line"); + } + } + + TEST(InputSpecLegacyTest, EnumClassSelection) + { + enum class EnumClass + { + A, + B, + C, + }; + + auto spec = all_of({ + deprecated_selection("enum", + { + {"A", EnumClass::A}, {"B", EnumClass::B}, + // Leave one out. Otherwise, we get an error to use a parameter instead. + }), + }); + + { + InputParameterContainer container; + std::string stream("enum A"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("enum"), EnumClass::A); + } + } + + TEST(InputSpecLegacyTest, MagicEnumParameter) + { + enum class EnumClass + { + A, + B, + C, + }; + + const auto describe = [](EnumClass e) -> std::string + { + switch (e) + { + case EnumClass::A: + return "The option A"; + default: + return "Other option"; + } + }; + + auto spec = parameter( + "enum", {.description = "An enum constant", .enum_value_description = describe}); + + { + SCOPED_TRACE("Valid enum constant"); + InputParameterContainer container; + std::string stream("enum A"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("enum"), EnumClass::A); + } + + { + SCOPED_TRACE("Invalid enum constant"); + InputParameterContainer container; + std::string stream("enum XYZ"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Could not parse value 'XYZ' as an enum constant of type 'EnumClass'"); + } + + { + std::ostringstream out; + ryml::Tree tree = init_yaml_tree_with_exceptions(); + ryml::NodeRef root = tree.rootref(); + YamlNodeRef yaml(root, ""); + spec.emit_metadata(yaml); + out << tree; + + std::string expected = R"(name: enum +type: enum +description: "An enum constant" +required: true +choices: + - name: A + description: "The option A" + - name: B + description: "Other option" + - name: C + description: "Other option" +)"; + EXPECT_EQ(out.str(), expected); + } + } + + TEST(InputSpecLegacyTest, ParseSingleDefaultedEntryDat) + { + // This used to be a bug where a single default dat parameter was not accepted. + auto spec = all_of({ + parameter("a", {.default_value = 1.0}), + }); + + { + InputParameterContainer container; + std::string stream(""); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1.0); + } + } + + TEST(InputSpecLegacyTest, Vector) + { + auto spec = all_of({ + parameter>>("a", {.size = {2, 2}}), + parameter>("b", {.size = 3}), + }); + + { + InputParameterContainer container; + std::string stream("a 1 2 3 4 b 1.0 2.0 3.0"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& a = container.get>>("a"); + EXPECT_EQ(a.size(), 2); + EXPECT_EQ(a[0].size(), 2); + EXPECT_EQ(a[0][0], 1); + EXPECT_EQ(a[0][1], 2); + EXPECT_EQ(a[1].size(), 2); + EXPECT_EQ(a[1][0], 3); + EXPECT_EQ(a[1][1], 4); + const auto& b = container.get>("b"); + EXPECT_EQ(b.size(), 3); + EXPECT_EQ(b[0], 1.0); + EXPECT_EQ(b[1], 2.0); + EXPECT_EQ(b[2], 3.0); + } + + { + InputParameterContainer container; + std::string stream("a 1 2 3 4 b 1.0 2.0 c"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Could not parse 'c' as a double value"); + } + } + + TEST(InputSpecLegacyTest, Optional) + { + auto spec = all_of({ + parameter("size"), + parameter>>("vector_none", + { + .default_value = std::vector>{std::nullopt, 1}, + .size = from_parameter("size"), + }), + parameter>>( + "none_vector", {.size = from_parameter("size")}), + parameter>("b", {.description = "b"}), + parameter>("e"), + }); + + { + SCOPED_TRACE("All values"); + InputParameterContainer container; + std::string stream("size 3 vector_none 1 2 3 b none none_vector 1 2 3"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& vector_none = container.get>>("vector_none"); + EXPECT_EQ(vector_none.size(), 3); + EXPECT_EQ(vector_none[0].has_value(), true); + EXPECT_EQ(vector_none[0].value(), 1); + EXPECT_EQ(vector_none[1].has_value(), true); + EXPECT_EQ(vector_none[1].value(), 2); + EXPECT_EQ(vector_none[2].has_value(), true); + EXPECT_EQ(vector_none[2].value(), 3); + + EXPECT_TRUE(container.get>>("none_vector").has_value()); + + const auto& b = container.get>("b"); + EXPECT_EQ(b.has_value(), false); + + const auto& e = container.get>("e"); + EXPECT_EQ(e.has_value(), false); + } + + { + SCOPED_TRACE("None values"); + InputParameterContainer container; + std::string stream("size 3 vector_none 1 none 3 b none e none none_vector none"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& a = container.get>>("vector_none"); + EXPECT_EQ(a.size(), 3); + EXPECT_EQ(a[0].has_value(), true); + EXPECT_EQ(a[0].value(), 1); + EXPECT_EQ(a[1].has_value(), false); + EXPECT_EQ(a[2].has_value(), true); + EXPECT_EQ(a[2].value(), 3); + + const auto& b = container.get>("b"); + EXPECT_EQ(b.has_value(), false); + + const auto& e = container.get>("e"); + EXPECT_EQ(e.has_value(), false); + } + + { + SCOPED_TRACE("Defaults"); + InputParameterContainer container; + std::string stream("size 3 b string e 42"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + + const auto& vector_none = container.get>>("vector_none"); + EXPECT_EQ(vector_none.size(), 2); + EXPECT_EQ(vector_none[0].has_value(), false); + EXPECT_EQ(vector_none[1].has_value(), true); + + const auto& b = container.get>("b"); + EXPECT_EQ(b.has_value(), true); + EXPECT_EQ(b.value(), "string"); + + const auto& e = container.get>("e"); + EXPECT_EQ(e.has_value(), true); + EXPECT_EQ(e.value(), 42); + } + } + + TEST(InputSpecLegacyTest, VectorWithParsedLength) + { + auto spec = all_of({ + parameter("a"), + parameter>("b", {.size = from_parameter("a")}), + parameter("c"), + }); + + { + InputParameterContainer container; + std::string stream("a 3 b 1.0 2.0 3.0 c string"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 3); + const auto& b = container.get>("b"); + EXPECT_EQ(b.size(), 3); + EXPECT_EQ(b[0], 1.0); + EXPECT_EQ(b[1], 2.0); + EXPECT_EQ(b[2], 3.0); + EXPECT_EQ(container.get("c"), "string"); + } + + { + InputParameterContainer container; + std::string stream("a 3 b 1.0 2.0 c string"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Could not parse 'c' as a double value"); + } + } + + TEST(InputSpecLegacyTest, EntryWithCallback) + { + auto spec = all_of({ + parameter("a"), + parameter("b"), + parameter("c", + { + .description = "A string", + .default_value = "Not found", + .on_parse_callback = [](InputParameterContainer& container) + { container.add("c_as_int", std::stoi(container.get("c"))); }, + }), + parameter("s"), + }); + + { + InputParameterContainer container; + std::string stream("a 1 b 2.0 c 10 s hello"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1); + EXPECT_EQ(container.get("b"), 2.0); + EXPECT_EQ(container.get("c"), "10"); + EXPECT_EQ(container.get("c_as_int"), 10); + EXPECT_EQ(container.get("s"), "hello"); + } + + { + InputParameterContainer container; + std::string stream("a 1 b 2.0 c _ hello"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE( + spec.deprecated_parse(parser, container), std::invalid_argument, "stoi"); + } + } + + TEST(InputSpecLegacyTest, Unparsed) + { + auto spec = all_of({ + parameter("a"), + parameter("optional", {.default_value = 42}), + parameter("b"), + parameter("c"), + }); + InputParameterContainer container; + std::string stream("a 1 b 2.0 c string unparsed unparsed"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "line still contains 'unparsed unparsed'"); + } + + + TEST(InputSpecLegacyTest, Groups) + { + auto spec = all_of({ + parameter("a"), + group("group1", + { + parameter("b"), + }), + group("group2", + { + parameter("b", {.default_value = 3.0}), + parameter("c"), + }, + { + .required = false, + }), + group("group3", + { + parameter("c", {.default_value = "default"}), + }, + { + .required = false, + }), + parameter("c"), + }); + + { + InputParameterContainer container; + std::string stream("a 1 group1 b 2.0 c string"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& const_container = container; + EXPECT_EQ(const_container.get("a"), 1); + EXPECT_EQ(const_container.group("group1").get("b"), 2.0); + EXPECT_ANY_THROW([[maybe_unused]] const auto& c = const_container.group("group2")); + // Group 3 only contains entries that have default values, so it implicitly has a default + // value. + EXPECT_EQ(const_container.group("group3").get("c"), "default"); + } + + { + InputParameterContainer container; + std::string stream("a 1 group2 b 2.0 c string group1 b 4.0 c string"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& const_container = container; + EXPECT_EQ(const_container.get("a"), 1); + EXPECT_EQ(const_container.group("group2").get("b"), 2.0); + EXPECT_EQ(const_container.group("group1").get("b"), 4.0); + EXPECT_EQ(const_container.get("c"), "string"); + } + + { + InputParameterContainer container; + std::string stream("a 1 group1 b 4.0 c string"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& const_container = container; + EXPECT_EQ(const_container.get("a"), 1); + EXPECT_ANY_THROW([[maybe_unused]] const auto& c = const_container.group("group2")); + EXPECT_EQ(const_container.group("group1").get("b"), 4.0); + EXPECT_EQ(const_container.get("c"), "string"); + } + } + + TEST(InputSpecLegacyTest, NestedAllOf) + { + auto spec = all_of({ + parameter("a"), + all_of({ + all_of({ + parameter("b"), + }), + // Not useful but might happen in practice, so ensure this can be handled. + all_of({}), + }), + }); + + { + InputParameterContainer container; + std::string stream("a 1 b 2.0"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + const auto& const_container = container; + EXPECT_EQ(const_container.get("a"), 1); + EXPECT_EQ(const_container.get("b"), 2.0); + } + } + + TEST(InputSpecLegacyTest, OneOf) + { + auto spec = all_of({ + parameter("a", {.default_value = 42}), + one_of({ + parameter("b"), + group("group", + { + parameter("c"), + parameter("d"), + }), + }), + }); + + { + InputParameterContainer container; + std::string stream("a 1 b 2"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1); + EXPECT_EQ(container.get("b"), 2); + } + + { + InputParameterContainer container; + std::string stream("group c string d 2.0"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 42); + EXPECT_EQ(container.group("group").get("c"), "string"); + EXPECT_EQ(container.group("group").get("d"), 2); + } + + { + InputParameterContainer container; + std::string stream("a 1 group c string d 2.0 b 3.0"); + ValueParser parser(stream); + // More than one of the one_of entries is present. Refuse to parse any of them. + FOUR_C_EXPECT_THROW_WITH_MESSAGE( + spec.deprecated_parse(parser, container), Core::Exception, "still contains 'b 3.0'"); + } + + { + InputParameterContainer container; + std::string stream("a 1 group c string"); + // Note: we start to parse the group, but the entries are not complete, so we backtrack. + // The result is that the parts of the group remain unparsed. + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Required 'one_of' not found in input line"); + } + + { + InputParameterContainer container; + std::string stream("a 1"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "Required 'one_of' not found in input line"); + } + } + + TEST(InputSpecLegacyTest, OneOfTopLevel) + { + auto spec = one_of( + { + all_of({ + parameter("a"), + parameter("b"), + }), + all_of({ + parameter("c"), + parameter("d"), + }), + }, + // Additionally store the index of the parsed group but map it to a different value. + store_index_as("index", /*reindex*/ {1, 10})); + + { + InputParameterContainer container; + std::string stream("a 1 b 2"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("a"), 1); + EXPECT_EQ(container.get("b"), 2); + EXPECT_EQ(container.get("index"), 1); + } + + { + InputParameterContainer container; + std::string stream("c string d 2.0"); + ValueParser parser(stream); + spec.deprecated_parse(parser, container); + EXPECT_EQ(container.get("c"), "string"); + EXPECT_EQ(container.get("d"), 2); + EXPECT_EQ(container.get("index"), 10); + } + + { + InputParameterContainer container; + std::string stream("a 1 b 2 c string d 2.0"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE( + spec.deprecated_parse(parser, container), Core::Exception, "Ambiguous input in one_of."); + } + + { + InputParameterContainer container; + std::string stream("a 1 c string"); + ValueParser parser(stream); + FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.deprecated_parse(parser, container), Core::Exception, + "None of the specs fit the input"); + } + } + + TEST(InputSpecLegacyTest, DatToYaml) + { + auto spec = all_of({ + // awkward one_of where the first choice partially matches + one_of({ + all_of({ + parameter("a"), + parameter("b"), + }), + all_of({ + parameter("a"), + parameter("d"), + }), + }), + // group with all defaulted entries + group("group", + { + parameter("c", {.default_value = 1.0}), + }, + {.required = false}), + list("list", + all_of({ + parameter("l1"), + parameter("l2"), + }), + {.size = 2}), + parameter("i", {.default_value = 0}), + parameter>("v", {.size = 3}), + }); + + std::string dat = "a 1 d 3.0 group c 1 i 42 v 1.0 2.0 3.0 list l1 1 l2 2.0 l1 3 l2 4.0"; + + InputParameterContainer container; + ValueParser parser(dat); + spec.deprecated_parse(parser, container); + + { + SCOPED_TRACE("Emit without default values"); + auto tree = init_yaml_tree_with_exceptions(); + YamlNodeRef yaml(tree.rootref(), ""); + spec.emit(yaml, container); + + std::ostringstream out; + out << tree; + std::string expected = R"(a: 1 +d: 3 +list: + - l1: 1 + l2: 2 + - l1: 3 + l2: 4 +i: 42 +v: [1,2,3] +)"; + EXPECT_EQ(out.str(), expected); + } + + { + SCOPED_TRACE("Emit with defaulted values"); + auto tree = init_yaml_tree_with_exceptions(); + YamlNodeRef yaml(tree.rootref(), ""); + spec.emit(yaml, container, {.emit_defaulted_values = true}); + + std::ostringstream out; + out << tree; + std::string expected = R"(a: 1 +d: 3 +group: + c: 1 +list: + - l1: 1 + l2: 2 + - l1: 3 + l2: 4 +i: 42 +v: [1,2,3] +)"; + EXPECT_EQ(out.str(), expected); + } + } + +} // namespace \ No newline at end of file diff --git a/src/core/io/tests/4C_io_input_spec_test.cpp b/src/core/io/tests/4C_io_input_spec_test.cpp index 96fb3b7e62..049d43fb8f 100644 --- a/src/core/io/tests/4C_io_input_spec_test.cpp +++ b/src/core/io/tests/4C_io_input_spec_test.cpp @@ -18,569 +18,6 @@ namespace using namespace Core::IO; using namespace Core::IO::InputSpecBuilders; - TEST(InputSpecTest, Simple) - { - auto spec = all_of({ - parameter("a", {.description = "An integer", .default_value = 1}), - parameter("b"), - parameter("d"), - }); - InputParameterContainer container; - std::string stream("b 2.0 d true // trailing comment"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), 2.0); - EXPECT_EQ(container.get("d"), true); - } - - TEST(InputSpecTest, OptionalLeftOut) - { - auto spec = all_of({ - parameter("a"), - parameter("b"), - parameter("c", {.default_value = "default"}), - }); - - { - InputParameterContainer container; - std::string stream("a 1 b 2.0 // c 1"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), 2.0); - EXPECT_EQ(container.get("c"), "default"); - } - } - - TEST(InputSpecTest, RequiredLeftOut) - { - auto spec = all_of({ - parameter("a"), - parameter("b"), - parameter("c"), - }); - - { - InputParameterContainer container; - std::string stream("a 1 b 2.0"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Required value 'c' not found in input line"); - } - } - - TEST(InputSpecTest, EnumClassSelection) - { - enum class EnumClass - { - A, - B, - C, - }; - - auto spec = all_of({ - deprecated_selection("enum", - { - {"A", EnumClass::A}, {"B", EnumClass::B}, - // Leave one out. Otherwise, we get an error to use a parameter instead. - }), - }); - - { - InputParameterContainer container; - std::string stream("enum A"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("enum"), EnumClass::A); - } - } - - TEST(InputSpecTest, MagicEnumParameter) - { - enum class EnumClass - { - A, - B, - C, - }; - - const auto describe = [](EnumClass e) -> std::string - { - switch (e) - { - case EnumClass::A: - return "The option A"; - default: - return "Other option"; - } - }; - - auto spec = parameter( - "enum", {.description = "An enum constant", .enum_value_description = describe}); - - { - SCOPED_TRACE("Valid enum constant"); - InputParameterContainer container; - std::string stream("enum A"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("enum"), EnumClass::A); - } - - { - SCOPED_TRACE("Invalid enum constant"); - InputParameterContainer container; - std::string stream("enum XYZ"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Could not parse value 'XYZ' as an enum constant of type 'EnumClass'"); - } - - { - std::ostringstream out; - ryml::Tree tree = init_yaml_tree_with_exceptions(); - ryml::NodeRef root = tree.rootref(); - YamlNodeRef yaml(root, ""); - spec.emit_metadata(yaml); - out << tree; - - std::string expected = R"(name: enum -type: enum -description: "An enum constant" -required: true -choices: - - name: A - description: "The option A" - - name: B - description: "Other option" - - name: C - description: "Other option" -)"; - EXPECT_EQ(out.str(), expected); - } - } - - TEST(InputSpecTest, ParseSingleDefaultedEntryDat) - { - // This used to be a bug where a single default dat parameter was not accepted. - auto spec = all_of({ - parameter("a", {.default_value = 1.0}), - }); - - { - InputParameterContainer container; - std::string stream(""); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1.0); - } - } - - TEST(InputSpecTest, Vector) - { - auto spec = all_of({ - parameter>>("a", {.size = {2, 2}}), - parameter>("b", {.size = 3}), - }); - - { - InputParameterContainer container; - std::string stream("a 1 2 3 4 b 1.0 2.0 3.0"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& a = container.get>>("a"); - EXPECT_EQ(a.size(), 2); - EXPECT_EQ(a[0].size(), 2); - EXPECT_EQ(a[0][0], 1); - EXPECT_EQ(a[0][1], 2); - EXPECT_EQ(a[1].size(), 2); - EXPECT_EQ(a[1][0], 3); - EXPECT_EQ(a[1][1], 4); - const auto& b = container.get>("b"); - EXPECT_EQ(b.size(), 3); - EXPECT_EQ(b[0], 1.0); - EXPECT_EQ(b[1], 2.0); - EXPECT_EQ(b[2], 3.0); - } - - { - InputParameterContainer container; - std::string stream("a 1 2 3 4 b 1.0 2.0 c"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Could not parse 'c' as a double value"); - } - } - - TEST(InputSpecTest, Optional) - { - auto spec = all_of({ - parameter("size"), - parameter>>("vector_none", - { - .default_value = std::vector>{std::nullopt, 1}, - .size = from_parameter("size"), - }), - parameter>>( - "none_vector", {.size = from_parameter("size")}), - parameter>("b", {.description = "b"}), - parameter>("e"), - }); - - { - SCOPED_TRACE("All values"); - InputParameterContainer container; - std::string stream("size 3 vector_none 1 2 3 b none none_vector 1 2 3"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& vector_none = container.get>>("vector_none"); - EXPECT_EQ(vector_none.size(), 3); - EXPECT_EQ(vector_none[0].has_value(), true); - EXPECT_EQ(vector_none[0].value(), 1); - EXPECT_EQ(vector_none[1].has_value(), true); - EXPECT_EQ(vector_none[1].value(), 2); - EXPECT_EQ(vector_none[2].has_value(), true); - EXPECT_EQ(vector_none[2].value(), 3); - - EXPECT_TRUE(container.get>>("none_vector").has_value()); - - const auto& b = container.get>("b"); - EXPECT_EQ(b.has_value(), false); - - const auto& e = container.get>("e"); - EXPECT_EQ(e.has_value(), false); - } - - { - SCOPED_TRACE("None values"); - InputParameterContainer container; - std::string stream("size 3 vector_none 1 none 3 b none e none none_vector none"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& a = container.get>>("vector_none"); - EXPECT_EQ(a.size(), 3); - EXPECT_EQ(a[0].has_value(), true); - EXPECT_EQ(a[0].value(), 1); - EXPECT_EQ(a[1].has_value(), false); - EXPECT_EQ(a[2].has_value(), true); - EXPECT_EQ(a[2].value(), 3); - - const auto& b = container.get>("b"); - EXPECT_EQ(b.has_value(), false); - - const auto& e = container.get>("e"); - EXPECT_EQ(e.has_value(), false); - } - - { - SCOPED_TRACE("Defaults"); - InputParameterContainer container; - std::string stream("size 3 b string e 42"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - - const auto& vector_none = container.get>>("vector_none"); - EXPECT_EQ(vector_none.size(), 2); - EXPECT_EQ(vector_none[0].has_value(), false); - EXPECT_EQ(vector_none[1].has_value(), true); - - const auto& b = container.get>("b"); - EXPECT_EQ(b.has_value(), true); - EXPECT_EQ(b.value(), "string"); - - const auto& e = container.get>("e"); - EXPECT_EQ(e.has_value(), true); - EXPECT_EQ(e.value(), 42); - } - } - - TEST(InputSpecTest, VectorWithParsedLength) - { - auto spec = all_of({ - parameter("a"), - parameter>("b", {.size = from_parameter("a")}), - parameter("c"), - }); - - { - InputParameterContainer container; - std::string stream("a 3 b 1.0 2.0 3.0 c string"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 3); - const auto& b = container.get>("b"); - EXPECT_EQ(b.size(), 3); - EXPECT_EQ(b[0], 1.0); - EXPECT_EQ(b[1], 2.0); - EXPECT_EQ(b[2], 3.0); - EXPECT_EQ(container.get("c"), "string"); - } - - { - InputParameterContainer container; - std::string stream("a 3 b 1.0 2.0 c string"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Could not parse 'c' as a double value"); - } - } - - TEST(InputSpecTest, EntryWithCallback) - { - auto spec = all_of({ - parameter("a"), - parameter("b"), - parameter("c", - { - .description = "A string", - .default_value = "Not found", - .on_parse_callback = [](InputParameterContainer& container) - { container.add("c_as_int", std::stoi(container.get("c"))); }, - }), - parameter("s"), - }); - - { - InputParameterContainer container; - std::string stream("a 1 b 2.0 c 10 s hello"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), 2.0); - EXPECT_EQ(container.get("c"), "10"); - EXPECT_EQ(container.get("c_as_int"), 10); - EXPECT_EQ(container.get("s"), "hello"); - } - - { - InputParameterContainer container; - std::string stream("a 1 b 2.0 c _ hello"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE( - spec.fully_parse(parser, container), std::invalid_argument, "stoi"); - } - } - - TEST(InputSpecTest, Unparsed) - { - auto spec = all_of({ - parameter("a"), - parameter("optional", {.default_value = 42}), - parameter("b"), - parameter("c"), - }); - InputParameterContainer container; - std::string stream("a 1 b 2.0 c string unparsed unparsed"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "line still contains 'unparsed unparsed'"); - } - - - TEST(InputSpecTest, Groups) - { - auto spec = all_of({ - parameter("a"), - group("group1", - { - parameter("b"), - }), - group("group2", - { - parameter("b", {.default_value = 3.0}), - parameter("c"), - }, - { - .required = false, - }), - group("group3", - { - parameter("c", {.default_value = "default"}), - }, - { - .required = false, - }), - parameter("c"), - }); - - { - InputParameterContainer container; - std::string stream("a 1 group1 b 2.0 c string"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& const_container = container; - EXPECT_EQ(const_container.get("a"), 1); - EXPECT_EQ(const_container.group("group1").get("b"), 2.0); - EXPECT_ANY_THROW([[maybe_unused]] const auto& c = const_container.group("group2")); - // Group 3 only contains entries that have default values, so it implicitly has a default - // value. - EXPECT_EQ(const_container.group("group3").get("c"), "default"); - } - - { - InputParameterContainer container; - std::string stream("a 1 group2 b 2.0 c string group1 b 4.0 c string"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& const_container = container; - EXPECT_EQ(const_container.get("a"), 1); - EXPECT_EQ(const_container.group("group2").get("b"), 2.0); - EXPECT_EQ(const_container.group("group1").get("b"), 4.0); - EXPECT_EQ(const_container.get("c"), "string"); - } - - { - InputParameterContainer container; - std::string stream("a 1 group1 b 4.0 c string"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& const_container = container; - EXPECT_EQ(const_container.get("a"), 1); - EXPECT_ANY_THROW([[maybe_unused]] const auto& c = const_container.group("group2")); - EXPECT_EQ(const_container.group("group1").get("b"), 4.0); - EXPECT_EQ(const_container.get("c"), "string"); - } - } - - TEST(InputSpecTest, NestedAllOf) - { - auto spec = all_of({ - parameter("a"), - all_of({ - all_of({ - parameter("b"), - }), - // Not useful but might happen in practice, so ensure this can be handled. - all_of({}), - }), - }); - - { - InputParameterContainer container; - std::string stream("a 1 b 2.0"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - const auto& const_container = container; - EXPECT_EQ(const_container.get("a"), 1); - EXPECT_EQ(const_container.get("b"), 2.0); - } - } - - TEST(InputSpecTest, OneOf) - { - auto spec = all_of({ - parameter("a", {.default_value = 42}), - one_of({ - parameter("b"), - group("group", - { - parameter("c"), - parameter("d"), - }), - }), - }); - - { - InputParameterContainer container; - std::string stream("a 1 b 2"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), 2); - } - - { - InputParameterContainer container; - std::string stream("group c string d 2.0"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 42); - EXPECT_EQ(container.group("group").get("c"), "string"); - EXPECT_EQ(container.group("group").get("d"), 2); - } - - { - InputParameterContainer container; - std::string stream("a 1 group c string d 2.0 b 3.0"); - ValueParser parser(stream); - // More than one of the one_of entries is present. Refuse to parse any of them. - FOUR_C_EXPECT_THROW_WITH_MESSAGE( - spec.fully_parse(parser, container), Core::Exception, "still contains 'b 3.0'"); - } - - { - InputParameterContainer container; - std::string stream("a 1 group c string"); - // Note: we start to parse the group, but the entries are not complete, so we backtrack. - // The result is that the parts of the group remain unparsed. - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Required 'one_of' not found in input line"); - } - - { - InputParameterContainer container; - std::string stream("a 1"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE(spec.fully_parse(parser, container), Core::Exception, - "Required 'one_of' not found in input line"); - } - } - - TEST(InputSpecTest, OneOfTopLevel) - { - auto spec = one_of( - { - all_of({ - parameter("a"), - parameter("b"), - }), - all_of({ - parameter("c"), - parameter("d"), - }), - }, - // Additionally store the index of the parsed group but map it to a different value. - store_index_as("index", /*reindex*/ {1, 10})); - - { - InputParameterContainer container; - std::string stream("a 1 b 2"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), 2); - EXPECT_EQ(container.get("index"), 1); - } - - { - InputParameterContainer container; - std::string stream("c string d 2.0"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("c"), "string"); - EXPECT_EQ(container.get("d"), 2); - EXPECT_EQ(container.get("index"), 10); - } - - { - InputParameterContainer container; - std::string stream("a 1 b 2 c string d 2.0"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE( - spec.fully_parse(parser, container), Core::Exception, "Ambiguous input in one_of."); - } - - { - InputParameterContainer container; - std::string stream("a 1 c string"); - ValueParser parser(stream); - FOUR_C_EXPECT_THROW_WITH_MESSAGE( - spec.fully_parse(parser, container), Core::Exception, "None of the specs fit the input"); - } - } - TEST(InputSpecTest, NestedOneOfs) { auto spec = one_of({ @@ -1248,20 +685,31 @@ required: true parameter("b"), }); - spec = all_of({ - tmp, - parameter("d"), - }); + spec = group("group", { + tmp, + parameter("d"), + }); } { InputParameterContainer container; - std::string stream("a 1 b string d 42"); - ValueParser parser(stream); - spec.fully_parse(parser, container); - EXPECT_EQ(container.get("a"), 1); - EXPECT_EQ(container.get("b"), "string"); - EXPECT_EQ(container.get("d"), 42); + auto tree = init_yaml_tree_with_exceptions(); + ryml::NodeRef root = tree.rootref(); + root |= ryml::MAP; + ryml::parse_in_arena(R"( +group: + a: 1 + b: string + d: 42 +)", + root); + + ConstYamlNodeRef node(root, ""); + spec.match(node, container); + const auto& group = container.group("group"); + EXPECT_EQ(group.get("a"), 1); + EXPECT_EQ(group.get("b"), "string"); + EXPECT_EQ(group.get("d"), 42); } } @@ -2271,86 +1719,6 @@ t: [[1, 2], [2, 3]])", } } - TEST(InputSpecTest, DatToYaml) - { - auto spec = all_of({ - // awkward one_of where the first choice partially matches - one_of({ - all_of({ - parameter("a"), - parameter("b"), - }), - all_of({ - parameter("a"), - parameter("d"), - }), - }), - // group with all defaulted entries - group("group", - { - parameter("c", {.default_value = 1.0}), - }, - {.required = false}), - list("list", - all_of({ - parameter("l1"), - parameter("l2"), - }), - {.size = 2}), - parameter("i", {.default_value = 0}), - parameter>("v", {.size = 3}), - }); - - std::string dat = "a 1 d 3.0 group c 1 i 42 v 1.0 2.0 3.0 list l1 1 l2 2.0 l1 3 l2 4.0"; - - InputParameterContainer container; - ValueParser parser(dat); - spec.fully_parse(parser, container); - - { - SCOPED_TRACE("Emit without default values"); - auto tree = init_yaml_tree_with_exceptions(); - YamlNodeRef yaml(tree.rootref(), ""); - spec.emit(yaml, container); - - std::ostringstream out; - out << tree; - std::string expected = R"(a: 1 -d: 3 -list: - - l1: 1 - l2: 2 - - l1: 3 - l2: 4 -i: 42 -v: [1,2,3] -)"; - EXPECT_EQ(out.str(), expected); - } - - { - SCOPED_TRACE("Emit with defaulted values"); - auto tree = init_yaml_tree_with_exceptions(); - YamlNodeRef yaml(tree.rootref(), ""); - spec.emit(yaml, container, {.emit_defaulted_values = true}); - - std::ostringstream out; - out << tree; - std::string expected = R"(a: 1 -d: 3 -group: - c: 1 -list: - - l1: 1 - l2: 2 - - l1: 3 - l2: 4 -i: 42 -v: [1,2,3] -)"; - EXPECT_EQ(out.str(), expected); - } - } TEST(InputSpecTest, ComplexMatchError) {