Index: include/llvm/Support/YAMLTraits.h =================================================================== --- include/llvm/Support/YAMLTraits.h +++ include/llvm/Support/YAMLTraits.h @@ -27,6 +27,8 @@ namespace llvm { namespace yaml { +struct EmptyContext {}; + /// This class should be specialized by any type that needs to be converted /// to/from a YAML mapping. For example: /// @@ -49,6 +51,28 @@ // static const bool flow = true; }; +/// This class is similar to MappingTraits but allows you to pass in +/// additional context for each map operation. For example: +/// +/// struct MappingContextTraits { +/// static void mapping(IO &io, MyStruct &s, MyContext &c) { +/// io.mapRequired("name", s.name); +/// io.mapRequired("size", s.size); +/// io.mapOptional("age", s.age); +/// ++c.TimesMapped; +/// } +/// }; +template struct MappingContextTraits { + // Must provide: + // static void mapping(IO &io, T &fields, Context &Ctx); + // Optionally may provide: + // static StringRef validate(IO &io, T &fields, Context &Ctx); + // + // The optional flow flag will cause generated YAML to use a flow mapping + // (e.g. { a: 0, b: 1 }): + // static const bool flow = true; +}; + /// This class should be specialized by any integral type that converts /// to/from a YAML scalar where there is a one-to-one mapping between /// in-memory values and a string in YAML. For example: @@ -258,11 +282,9 @@ (sizeof(test>(nullptr, nullptr)) == 1); }; -// Test if MappingTraits is defined on type T. -template -struct has_MappingTraits -{ - typedef void (*Signature_mapping)(class IO&, T&); +// Test if MappingContextTraits is defined on type T. +template struct has_MappingTraits { + typedef void (*Signature_mapping)(class IO &, T &, Context &); template static char test(SameType*); @@ -271,14 +293,26 @@ static double test(...); public: - static bool const value = (sizeof(test >(nullptr)) == 1); + static bool const value = + (sizeof(test>(nullptr)) == 1); +}; + +// Test if MappingTraits is defined on type T. +template struct has_MappingTraits { + typedef void (*Signature_mapping)(class IO &, T &); + + template + static char test(SameType *); + + template static double test(...); + +public: + static bool const value = (sizeof(test>(nullptr)) == 1); }; -// Test if MappingTraits::validate() is defined on type T. -template -struct has_MappingValidateTraits -{ - typedef StringRef (*Signature_validate)(class IO&, T&); +// Test if MappingContextTraits::validate() is defined on type T. +template struct has_MappingValidateTraits { + typedef StringRef (*Signature_validate)(class IO &, T &, Context &); template static char test(SameType*); @@ -287,7 +321,21 @@ static double test(...); public: - static bool const value = (sizeof(test >(nullptr)) == 1); + static bool const value = + (sizeof(test>(nullptr)) == 1); +}; + +// Test if MappingTraits::validate() is defined on type T. +template struct has_MappingValidateTraits { + typedef StringRef (*Signature_validate)(class IO &, T &); + + template + static char test(SameType *); + + template static double test(...); + +public: + static bool const value = (sizeof(test>(nullptr)) == 1); }; // Test if SequenceTraits is defined on type T. @@ -432,25 +480,29 @@ return false; } -template -struct missingTraits : public std::integral_constant::value - && !has_ScalarBitSetTraits::value - && !has_ScalarTraits::value - && !has_BlockScalarTraits::value - && !has_MappingTraits::value - && !has_SequenceTraits::value - && !has_DocumentListTraits::value > {}; +template +struct missingTraits + : public std::integral_constant::value && + !has_ScalarBitSetTraits::value && + !has_ScalarTraits::value && + !has_BlockScalarTraits::value && + !has_MappingTraits::value && + !has_SequenceTraits::value && + !has_DocumentListTraits::value> {}; + +template +struct validatedMappingTraits + : public std::integral_constant< + bool, has_MappingTraits::value && + has_MappingValidateTraits::value> {}; + +template +struct unvalidatedMappingTraits + : public std::integral_constant< + bool, has_MappingTraits::value && + !has_MappingValidateTraits::value> {}; -template -struct validatedMappingTraits : public std::integral_constant::value - && has_MappingValidateTraits::value> {}; - -template -struct unvalidatedMappingTraits : public std::integral_constant::value - && !has_MappingValidateTraits::value> {}; // Base class for Input and Output. class IO { public: @@ -550,40 +602,58 @@ void *getContext(); void setContext(void *); - template - void mapRequired(const char* Key, T& Val) { - this->processKey(Key, Val, true); + template void mapRequired(const char *Key, T &Val) { + EmptyContext Ctx; + this->processKey(Key, Val, true, Ctx); + } + template + void mapRequired(const char *Key, T &Val, Context &Ctx) { + this->processKey(Key, Val, true, Ctx); + } + + template void mapOptional(const char *Key, T &Val) { + EmptyContext Ctx; + mapOptionalWithContext(Key, Val, Ctx); } template - typename std::enable_if::value,void>::type - mapOptional(const char* Key, T& Val) { + void mapOptional(const char *Key, T &Val, const T &Default) { + EmptyContext Ctx; + mapOptionalWithContext(Key, Val, Default, Ctx); + } + + template + typename std::enable_if::value, void>::type + mapOptionalWithContext(const char *Key, T &Val, Context &Ctx) { // omit key/value instead of outputting empty sequence - if ( this->canElideEmptySequence() && !(Val.begin() != Val.end()) ) + if (this->canElideEmptySequence() && !(Val.begin() != Val.end())) return; - this->processKey(Key, Val, false); + this->processKey(Key, Val, false, Ctx); } - template - void mapOptional(const char* Key, Optional &Val) { - processKeyWithDefault(Key, Val, Optional(), /*Required=*/false); + template + void mapOptionalWithContext(const char *Key, Optional &Val, Context &Ctx) { + this->processKeyWithDefault(Key, Val, Optional(), /*Required=*/false, + Ctx); } - template - typename std::enable_if::value,void>::type - mapOptional(const char* Key, T& Val) { - this->processKey(Key, Val, false); + template + typename std::enable_if::value, void>::type + mapOptionalWithContext(const char *Key, T &Val, Context &Ctx) { + this->processKey(Key, Val, false, Ctx); } - template - void mapOptional(const char* Key, T& Val, const T& Default) { - this->processKeyWithDefault(Key, Val, Default, false); + template + void mapOptionalWithContext(const char *Key, T &Val, const T &Default, + Context &Ctx) { + this->processKeyWithDefault(Key, Val, Default, false, Ctx); } private: - template + template void processKeyWithDefault(const char *Key, Optional &Val, - const Optional &DefaultValue, bool Required) { + const Optional &DefaultValue, bool Required, + Context &Ctx) { assert(DefaultValue.hasValue() == false && "Optional shouldn't have a value!"); void *SaveInfo; @@ -593,7 +663,7 @@ Val = T(); if (this->preflightKey(Key, Required, sameAsDefault, UseDefault, SaveInfo)) { - yamlize(*this, Val.getValue(), Required); + yamlize(*this, Val.getValue(), Required, Ctx); this->postflightKey(SaveInfo); } else { if (UseDefault) @@ -601,15 +671,15 @@ } } - template - void processKeyWithDefault(const char *Key, T &Val, const T& DefaultValue, - bool Required) { + template + void processKeyWithDefault(const char *Key, T &Val, const T &DefaultValue, + bool Required, Context &Ctx) { void *SaveInfo; bool UseDefault; const bool sameAsDefault = outputting() && Val == DefaultValue; if ( this->preflightKey(Key, Required, sameAsDefault, UseDefault, SaveInfo) ) { - yamlize(*this, Val, Required); + yamlize(*this, Val, Required, Ctx); this->postflightKey(SaveInfo); } else { @@ -618,12 +688,12 @@ } } - template - void processKey(const char *Key, T &Val, bool Required) { + template + void processKey(const char *Key, T &Val, bool Required, Context &Ctx) { void *SaveInfo; bool UseDefault; if ( this->preflightKey(Key, Required, false, UseDefault, SaveInfo) ) { - yamlize(*this, Val, Required); + yamlize(*this, Val, Required, Ctx); this->postflightKey(SaveInfo); } } @@ -632,17 +702,17 @@ void *Ctxt; }; -template -typename std::enable_if::value,void>::type -yamlize(IO &io, T &Val, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { io.beginEnumScalar(); ScalarEnumerationTraits::enumeration(io, Val); io.endEnumScalar(); } -template -typename std::enable_if::value,void>::type -yamlize(IO &io, T &Val, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { bool DoClear; if ( io.beginBitSetScalar(DoClear) ) { if ( DoClear ) @@ -652,9 +722,9 @@ } } -template -typename std::enable_if::value,void>::type -yamlize(IO &io, T &Val, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { if ( io.outputting() ) { std::string Storage; llvm::raw_string_ostream Buffer(Storage); @@ -674,7 +744,7 @@ template typename std::enable_if::value, void>::type -yamlize(IO &YamlIO, T &Val, bool) { +yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) { if (YamlIO.outputting()) { std::string Storage; llvm::raw_string_ostream Buffer(Storage); @@ -691,9 +761,18 @@ } } -template -typename std::enable_if::value, void>::type -yamlize(IO &io, T &Val, bool) { +template +void do_mapping(IO &io, T &Val, Context &Ctx) { + MappingContextTraits::mapping(io, Val, Ctx); +} + +template void do_mapping(IO &io, T &Val, EmptyContext &Ctx) { + MappingTraits::mapping(io, Val); +} + +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, Context &Ctx) { if (has_FlowTraits>::value) io.beginFlowMapping(); else @@ -705,7 +784,7 @@ assert(Err.empty() && "invalid struct trying to be written as yaml"); } } - MappingTraits::mapping(io, Val); + do_mapping(io, Val, Ctx); if (!io.outputting()) { StringRef Err = MappingTraits::validate(io, Val); if (!Err.empty()) @@ -717,36 +796,36 @@ io.endMapping(); } -template -typename std::enable_if::value, void>::type -yamlize(IO &io, T &Val, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, Context &Ctx) { if (has_FlowTraits>::value) { io.beginFlowMapping(); - MappingTraits::mapping(io, Val); + do_mapping(io, Val, Ctx); io.endFlowMapping(); } else { io.beginMapping(); - MappingTraits::mapping(io, Val); + do_mapping(io, Val, Ctx); io.endMapping(); } } -template -typename std::enable_if::value, void>::type -yamlize(IO &io, T &Val, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { char missing_yaml_trait_for_type[sizeof(MissingTrait)]; } -template -typename std::enable_if::value,void>::type -yamlize(IO &io, T &Seq, bool) { +template +typename std::enable_if::value, void>::type +yamlize(IO &io, T &Seq, bool, Context &Ctx) { if ( has_FlowTraits< SequenceTraits >::value ) { unsigned incnt = io.beginFlowSequence(); unsigned count = io.outputting() ? SequenceTraits::size(io, Seq) : incnt; for(unsigned i=0; i < count; ++i) { void *SaveInfo; if ( io.preflightFlowElement(i, SaveInfo) ) { - yamlize(io, SequenceTraits::element(io, Seq, i), true); + yamlize(io, SequenceTraits::element(io, Seq, i), true, Ctx); io.postflightFlowElement(SaveInfo); } } @@ -758,7 +837,7 @@ for(unsigned i=0; i < count; ++i) { void *SaveInfo; if ( io.preflightElement(i, SaveInfo) ) { - yamlize(io, SequenceTraits::element(io, Seq, i), true); + yamlize(io, SequenceTraits::element(io, Seq, i), true, Ctx); io.postflightElement(SaveInfo); } } @@ -1241,8 +1320,9 @@ typename std::enable_if::value, Input &>::type operator>>(Input &yin, T &docList) { int i = 0; + EmptyContext Ctx; while ( yin.setCurrentDocument() ) { - yamlize(yin, DocumentListTraits::element(yin, docList, i), true); + yamlize(yin, DocumentListTraits::element(yin, docList, i), true, Ctx); if ( yin.error() ) return yin; yin.nextDocument(); @@ -1253,11 +1333,12 @@ // Define non-member operator>> so that Input can stream in a map as a document. template -inline -typename std::enable_if::value, Input &>::type +inline typename std::enable_if::value, + Input &>::type operator>>(Input &yin, T &docMap) { + EmptyContext Ctx; yin.setCurrentDocument(); - yamlize(yin, docMap, true); + yamlize(yin, docMap, true, Ctx); return yin; } @@ -1267,8 +1348,9 @@ inline typename std::enable_if::value, Input &>::type operator>>(Input &yin, T &docSeq) { + EmptyContext Ctx; if (yin.setCurrentDocument()) - yamlize(yin, docSeq, true); + yamlize(yin, docSeq, true, Ctx); return yin; } @@ -1277,15 +1359,16 @@ inline typename std::enable_if::value, Input &>::type operator>>(Input &In, T &Val) { + EmptyContext Ctx; if (In.setCurrentDocument()) - yamlize(In, Val, true); + yamlize(In, Val, true, Ctx); return In; } // Provide better error message about types missing a trait specialization template -inline -typename std::enable_if::value, Input &>::type +inline typename std::enable_if::value, + Input &>::type operator>>(Input &yin, T &docSeq) { char missing_yaml_trait_for_type[sizeof(MissingTrait)]; return yin; @@ -1296,11 +1379,13 @@ inline typename std::enable_if::value, Output &>::type operator<<(Output &yout, T &docList) { + EmptyContext Ctx; yout.beginDocuments(); const size_t count = DocumentListTraits::size(yout, docList); for(size_t i=0; i < count; ++i) { if ( yout.preflightDocument(i) ) { - yamlize(yout, DocumentListTraits::element(yout, docList, i), true); + yamlize(yout, DocumentListTraits::element(yout, docList, i), true, + Ctx); yout.postflightDocument(); } } @@ -1310,12 +1395,13 @@ // Define non-member operator<< so that Output can stream out a map. template -inline -typename std::enable_if::value, Output &>::type +inline typename std::enable_if::value, + Output &>::type operator<<(Output &yout, T &map) { + EmptyContext Ctx; yout.beginDocuments(); if ( yout.preflightDocument(0) ) { - yamlize(yout, map, true); + yamlize(yout, map, true, Ctx); yout.postflightDocument(); } yout.endDocuments(); @@ -1327,9 +1413,10 @@ inline typename std::enable_if::value, Output &>::type operator<<(Output &yout, T &seq) { + EmptyContext Ctx; yout.beginDocuments(); if ( yout.preflightDocument(0) ) { - yamlize(yout, seq, true); + yamlize(yout, seq, true, Ctx); yout.postflightDocument(); } yout.endDocuments(); @@ -1341,9 +1428,10 @@ 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); + yamlize(Out, Val, true, Ctx); Out.postflightDocument(); } Out.endDocuments(); @@ -1352,8 +1440,8 @@ // Provide better error message about types missing a trait specialization template -inline -typename std::enable_if::value, Output &>::type +inline typename std::enable_if::value, + Output &>::type operator<<(Output &yout, T &seq) { char missing_yaml_trait_for_type[sizeof(MissingTrait)]; return yout; Index: unittests/Support/YAMLIOTest.cpp =================================================================== --- unittests/Support/YAMLIOTest.cpp +++ unittests/Support/YAMLIOTest.cpp @@ -2299,3 +2299,72 @@ out.clear(); } } + +namespace llvm { +namespace yaml { +struct MappingContext { + int A = 0; +}; +struct SimpleMap { + int B = 0; + int C = 0; +}; + +struct NestedMap { + NestedMap(MappingContext &Context) : Context(Context) {} + SimpleMap Simple; + MappingContext &Context; +}; + +template <> struct MappingContextTraits { + static void mapping(IO &io, SimpleMap &sm, MappingContext &Context) { + io.mapRequired("B", sm.B); + io.mapRequired("C", sm.C); + ++Context.A; + io.mapRequired("Context", Context.A); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, NestedMap &nm) { + io.mapRequired("Simple", nm.Simple, nm.Context); + } +}; +} +} + +TEST(YAMLIO, TestMapWithContext) { + llvm::yaml::MappingContext Context; + llvm::yaml::NestedMap Nested(Context); + std::string out; + llvm::raw_string_ostream ostr(out); + + Output yout(ostr, nullptr, 15); + + yout << Nested; + ostr.flush(); + EXPECT_EQ(1, Context.A); + EXPECT_EQ("---\n" + "Simple: \n" + " B: 0\n" + " C: 0\n" + " Context: 1\n" + "...\n", + out); + + out.clear(); + + Nested.Simple.B = 2; + Nested.Simple.C = 3; + yout << Nested; + ostr.flush(); + EXPECT_EQ(2, Context.A); + EXPECT_EQ("---\n" + "Simple: \n" + " B: 2\n" + " C: 3\n" + " Context: 2\n" + "...\n", + out); + out.clear(); +}