Index: llvm/include/llvm/Support/YAMLTraits.h =================================================================== --- llvm/include/llvm/Support/YAMLTraits.h +++ llvm/include/llvm/Support/YAMLTraits.h @@ -209,6 +209,15 @@ // static T::value_type& element(IO &io, T &seq, size_t index); }; +/// This class should be specialized by any type that needs to be converted +/// to/from a YAML mapping in the case where the names of the keys are not known +/// in advance, i.e. a string map. +template +struct StringMapTraits { + // static void inputOne(IO &io, StringRef key, T &elem); + // static void output(IO &io, T &elem); +}; + // Only used for better diagnostics of missing traits template struct MissingTrait; @@ -358,6 +367,22 @@ static bool const value = (sizeof(test>(nullptr)) == 1); }; +// Test if StringMapTraits is defined on type T. +template +struct has_StringMapTraits +{ + typedef void (*Signature_input)(IO &io, StringRef key, T &v); + + template + static char test(SameType*); + + template + static double test(...); + +public: + static bool const value = (sizeof(test>(nullptr)) == 1); +}; + // has_FlowTraits will cause an error with some compilers because // it subclasses int. Using this wrapper only instantiates the // real has_FlowTraits only if the template type is a class. @@ -493,6 +518,7 @@ !has_BlockScalarTraits::value && !has_MappingTraits::value && !has_SequenceTraits::value && + !has_StringMapTraits::value && !has_DocumentListTraits::value> {}; template @@ -531,6 +557,7 @@ virtual void endMapping() = 0; virtual bool preflightKey(const char*, bool, bool, bool &, void *&) = 0; virtual void postflightKey(void*) = 0; + virtual std::vector keys() = 0; virtual void beginFlowMapping() = 0; virtual void endFlowMapping() = 0; @@ -819,6 +846,21 @@ } template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { + if ( io.outputting() ) { + io.beginMapping(); + StringMapTraits::output(io, Val); + io.endMapping(); + } else { + io.beginMapping(); + for (StringRef key : io.keys()) + StringMapTraits::inputOne(io, key, Val); + io.endMapping(); + } +} + +template typename std::enable_if::value, void>::type yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { char missing_yaml_trait_for_type[sizeof(MissingTrait)]; @@ -1074,6 +1116,7 @@ void endMapping() override; bool preflightKey(const char *, bool, bool, bool &, void *&) override; void postflightKey(void *) override; + std::vector keys() override; void beginFlowMapping() override; void endFlowMapping() override; unsigned beginSequence() override; @@ -1157,7 +1200,7 @@ bool isValidKey(StringRef key); NameToNode Mapping; - llvm::SmallVector ValidKeys; + llvm::SmallVector ValidKeys; }; class SequenceHNode : public HNode { @@ -1215,6 +1258,7 @@ void endMapping() override; bool preflightKey(const char *key, bool, bool, bool &, void *&) override; void postflightKey(void *) override; + std::vector keys() override; void beginFlowMapping() override; void endFlowMapping() override; unsigned beginSequence() override; @@ -1384,6 +1428,17 @@ return In; } +// Define non-member operator>> so that Input can stream in a string map. +template +inline +typename std::enable_if::value, Input &>::type +operator>>(Input &In, T &Val) { + EmptyContext Ctx; + if (In.setCurrentDocument()) + yamlize(In, Val, true, Ctx); + return In; +} + // Provide better error message about types missing a trait specialization template inline typename std::enable_if::value, @@ -1457,6 +1512,21 @@ return Out; } +// Define non-member operator<< so that Output can stream out a string map. +template +inline +typename std::enable_if::value, Output &>::type +operator<<(Output &Out, T &Val) { + EmptyContext Ctx; + Out.beginDocuments(); + if (Out.preflightDocument(0)) { + yamlize(Out, Val, true, Ctx); + Out.postflightDocument(); + } + Out.endDocuments(); + return Out; +} + // Provide better error message about types missing a trait specialization template inline typename std::enable_if::value, @@ -1476,6 +1546,16 @@ } }; +template struct StringMapTraitsImpl { + static void inputOne(IO &io, StringRef key, T &v) { + io.mapRequired(key.str().c_str(), v[key]); + } + static void output(IO &io, T &v) { + for (auto &p : v) + io.mapRequired(p.first.c_str(), p.second); + } +}; + } // end namespace yaml } // end namespace llvm @@ -1530,4 +1610,15 @@ } \ } +/// Utility for declaring that std::map should be considered +/// a YAML map. +#define LLVM_YAML_IS_STRING_MAP(_type) \ + namespace llvm { \ + namespace yaml { \ + template <> \ + struct StringMapTraits> \ + : public StringMapTraitsImpl> {}; \ + } \ + } + #endif // LLVM_SUPPORT_YAMLTRAITS_H Index: llvm/lib/Support/YAMLTraits.cpp =================================================================== --- llvm/lib/Support/YAMLTraits.cpp +++ llvm/lib/Support/YAMLTraits.cpp @@ -118,6 +118,18 @@ } } +std::vector Input::keys() { + MapHNode *MN = dyn_cast(CurrentNode); + std::vector Ret; + if (!MN) { + setError(CurrentNode, "not a mapping"); + return Ret; + } + for (auto &P : MN->Mapping) + Ret.push_back(P.first()); + return Ret; +} + bool Input::preflightKey(const char *Key, bool Required, bool, bool &UseDefault, void *&SaveInfo) { UseDefault = false; @@ -374,8 +386,8 @@ } bool Input::MapHNode::isValidKey(StringRef Key) { - for (const char *K : ValidKeys) { - if (Key.equals(K)) + for (std::string &K : ValidKeys) { + if (Key == K) return true; } return false; @@ -451,6 +463,10 @@ StateStack.pop_back(); } +std::vector Output::keys() { + report_fatal_error("invalid call"); +} + bool Output::preflightKey(const char *Key, bool Required, bool SameAsDefault, bool &UseDefault, void *&) { UseDefault = false; Index: llvm/unittests/Support/YAMLIOTest.cpp =================================================================== --- llvm/unittests/Support/YAMLIOTest.cpp +++ llvm/unittests/Support/YAMLIOTest.cpp @@ -2369,6 +2369,68 @@ out.clear(); } +LLVM_YAML_IS_STRING_MAP(int) + +TEST(YAMLIO, TestStringMap) { + std::map x; + x["foo"] = 1; + x["bar"] = 2; + + std::string out; + llvm::raw_string_ostream ostr(out); + Output xout(ostr, nullptr, 0); + + xout << x; + ostr.flush(); + EXPECT_EQ("---\n" + "bar: 2\n" + "foo: 1\n" + "...\n", + out); + + Input yin(out); + std::map y; + yin >> y; + EXPECT_EQ(2ul, y.size()); + EXPECT_EQ(1, y["foo"]); + EXPECT_EQ(2, y["bar"]); +} + +LLVM_YAML_IS_STRING_MAP(FooBar) + +TEST(YAMLIO, TestStringMapStruct) { + std::map x; + x["foo"].foo = 1; + x["foo"].bar = 2; + x["bar"].foo = 3; + x["bar"].bar = 4; + + std::string out; + llvm::raw_string_ostream ostr(out); + Output xout(ostr, nullptr, 0); + + xout << x; + ostr.flush(); + EXPECT_EQ("---\n" + "bar: \n" + " foo: 3\n" + " bar: 4\n" + "foo: \n" + " foo: 1\n" + " bar: 2\n" + "...\n", + out); + + Input yin(out); + std::map y; + yin >> y; + EXPECT_EQ(2ul, y.size()); + EXPECT_EQ(1, y["foo"].foo); + EXPECT_EQ(2, y["foo"].bar); + EXPECT_EQ(3, y["bar"].foo); + EXPECT_EQ(4, y["bar"].bar); +} + TEST(YAMLIO, InvalidInput) { // polluting 1 value in the sequence Input yin("---\n- foo: 3\n bar: 5\n1\n- foo: 3\n bar: 5\n...\n");