Support: Add YAML I/O support for custom mappings.

This will be used to YAMLify parts of the module summary.

Differential Revision: https://reviews.llvm.org/D28014

llvm-svn: 290935
This commit is contained in:
Peter Collingbourne 2017-01-04 03:51:36 +00:00
parent 5f0793b36c
commit 87dd2ab000
3 changed files with 175 additions and 3 deletions

View File

@ -209,6 +209,15 @@ struct DocumentListTraits {
// 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, e.g. a string map.
template <typename T>
struct CustomMappingTraits {
// 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 <typename T>
struct MissingTrait;
@ -358,6 +367,23 @@ public:
static bool const value = (sizeof(test<SequenceTraits<T>>(nullptr)) == 1);
};
// Test if CustomMappingTraits<T> is defined on type T.
template <class T>
struct has_CustomMappingTraits
{
typedef void (*Signature_input)(IO &io, StringRef key, T &v);
template <typename U>
static char test(SameType<Signature_input, &U::inputOne>*);
template <typename U>
static double test(...);
public:
static bool const value =
(sizeof(test<CustomMappingTraits<T>>(nullptr)) == 1);
};
// has_FlowTraits<int> 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 +519,7 @@ struct missingTraits
!has_BlockScalarTraits<T>::value &&
!has_MappingTraits<T, Context>::value &&
!has_SequenceTraits<T>::value &&
!has_CustomMappingTraits<T>::value &&
!has_DocumentListTraits<T>::value> {};
template <typename T, typename Context>
@ -531,6 +558,7 @@ public:
virtual void endMapping() = 0;
virtual bool preflightKey(const char*, bool, bool, bool &, void *&) = 0;
virtual void postflightKey(void*) = 0;
virtual std::vector<StringRef> keys() = 0;
virtual void beginFlowMapping() = 0;
virtual void endFlowMapping() = 0;
@ -818,6 +846,21 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) {
}
}
template <typename T>
typename std::enable_if<has_CustomMappingTraits<T>::value, void>::type
yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
if ( io.outputting() ) {
io.beginMapping();
CustomMappingTraits<T>::output(io, Val);
io.endMapping();
} else {
io.beginMapping();
for (StringRef key : io.keys())
CustomMappingTraits<T>::inputOne(io, key, Val);
io.endMapping();
}
}
template <typename T>
typename std::enable_if<missingTraits<T, EmptyContext>::value, void>::type
yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
@ -1074,6 +1117,7 @@ private:
void endMapping() override;
bool preflightKey(const char *, bool, bool, bool &, void *&) override;
void postflightKey(void *) override;
std::vector<StringRef> keys() override;
void beginFlowMapping() override;
void endFlowMapping() override;
unsigned beginSequence() override;
@ -1157,7 +1201,7 @@ private:
bool isValidKey(StringRef key);
NameToNode Mapping;
llvm::SmallVector<const char*, 6> ValidKeys;
llvm::SmallVector<std::string, 6> ValidKeys;
};
class SequenceHNode : public HNode {
@ -1215,6 +1259,7 @@ public:
void endMapping() override;
bool preflightKey(const char *key, bool, bool, bool &, void *&) override;
void postflightKey(void *) override;
std::vector<StringRef> keys() override;
void beginFlowMapping() override;
void endFlowMapping() override;
unsigned beginSequence() override;
@ -1384,6 +1429,17 @@ operator>>(Input &In, T &Val) {
return In;
}
// Define non-member operator>> so that Input can stream in a string map.
template <typename T>
inline
typename std::enable_if<has_CustomMappingTraits<T>::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 <typename T>
inline typename std::enable_if<missingTraits<T, EmptyContext>::value,
@ -1457,6 +1513,21 @@ operator<<(Output &Out, T &Val) {
return Out;
}
// Define non-member operator<< so that Output can stream out a string map.
template <typename T>
inline
typename std::enable_if<has_CustomMappingTraits<T>::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 <typename T>
inline typename std::enable_if<missingTraits<T, EmptyContext>::value,
@ -1476,6 +1547,18 @@ template <typename T> struct SequenceTraitsImpl {
}
};
/// Implementation of CustomMappingTraits for std::map<std::string, T>.
template <typename T> struct StdMapStringCustomMappingTraitsImpl {
typedef std::map<std::string, T> map_type;
static void inputOne(IO &io, StringRef key, map_type &v) {
io.mapRequired(key.str().c_str(), v[key]);
}
static void output(IO &io, map_type &v) {
for (auto &p : v)
io.mapRequired(p.first.c_str(), p.second);
}
};
} // end namespace yaml
} // end namespace llvm
@ -1530,4 +1613,15 @@ template <typename T> struct SequenceTraitsImpl {
} \
}
/// Utility for declaring that std::map<std::string, _type> should be considered
/// a YAML map.
#define LLVM_YAML_IS_STRING_MAP(_type) \
namespace llvm { \
namespace yaml { \
template <> \
struct CustomMappingTraits<std::map<std::string, _type>> \
: public StdMapStringCustomMappingTraitsImpl<_type> {}; \
} \
}
#endif // LLVM_SUPPORT_YAMLTRAITS_H

View File

@ -118,6 +118,18 @@ void Input::beginMapping() {
}
}
std::vector<StringRef> Input::keys() {
MapHNode *MN = dyn_cast<MapHNode>(CurrentNode);
std::vector<StringRef> 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 @@ std::unique_ptr<Input::HNode> Input::createHNodes(Node *N) {
}
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 @@ void Output::endMapping() {
StateStack.pop_back();
}
std::vector<StringRef> Output::keys() {
report_fatal_error("invalid call");
}
bool Output::preflightKey(const char *Key, bool Required, bool SameAsDefault,
bool &UseDefault, void *&) {
UseDefault = false;

View File

@ -2369,6 +2369,68 @@ TEST(YAMLIO, TestMapWithContext) {
out.clear();
}
LLVM_YAML_IS_STRING_MAP(int)
TEST(YAMLIO, TestCustomMapping) {
std::map<std::string, int> 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<std::string, int> 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, TestCustomMappingStruct) {
std::map<std::string, FooBar> 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<std::string, FooBar> 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");