Skip to content

Commit

Permalink
Support: Add YAML I/O support for custom mappings.
Browse files Browse the repository at this point in the history
This will be used to YAMLify parts of the module summary.

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

llvm-svn: 290935
  • Loading branch information
pcc committed Jan 4, 2017
1 parent 5f0793b commit 87dd2ab
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 3 deletions.
96 changes: 95 additions & 1 deletion llvm/include/llvm/Support/YAMLTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -358,6 +367,23 @@ struct has_SequenceMethodTraits
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.
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -531,6 +558,7 @@ class IO {
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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1074,6 +1117,7 @@ class Input : public IO {
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;
Expand Down Expand Up @@ -1157,7 +1201,7 @@ class Input : public IO {
bool isValidKey(StringRef key);

NameToNode Mapping;
llvm::SmallVector<const char*, 6> ValidKeys;
llvm::SmallVector<std::string, 6> ValidKeys;
};

class SequenceHNode : public HNode {
Expand Down Expand Up @@ -1215,6 +1259,7 @@ class Output : public IO {
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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -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
20 changes: 18 additions & 2 deletions llvm/lib/Support/YAMLTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
62 changes: 62 additions & 0 deletions llvm/unittests/Support/YAMLIOTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit 87dd2ab

Please sign in to comment.