Commit db54a909 authored by Tom Jackson's avatar Tom Jackson Committed by Owen Yamauchi

eachToTuple<Types...>()

Summary: For parsing records. `eachToPair` might be a worthwhile specialization.

Test Plan: Unit tests, benchmarks maybe?

Reviewed By: antoine@fb.com

FB internal diff: D827441
parent c1c28d91
......@@ -220,6 +220,15 @@ class To {
}
};
// Specialization to allow String->StringPiece conversion
template <>
class To<StringPiece> {
public:
StringPiece operator()(StringPiece src) const {
return src;
}
};
namespace detail {
template<class Self>
......
......@@ -216,6 +216,48 @@ class UnsplitBuffer : public Operator<UnsplitBuffer<Delimiter, OutputBuffer>> {
};
/**
* Hack for static for-like constructs
*/
template<class Target, class=void>
inline Target passthrough(Target target) { return target; }
/**
* ParseToTuple - For splitting a record and immediatlely converting it to a
* target tuple type. Primary used through the 'eachToTuple' helper, like so:
*
* auto config
* = split("1:a 2:b", ' ')
* | eachToTuple<int, string>()
* | as<vector<tuple<int, string>>>();
*
*/
template<class TargetContainer,
class Delimiter,
class... Targets>
class SplitTo {
Delimiter delimiter_;
public:
explicit SplitTo(Delimiter delimiter)
: delimiter_(delimiter) {}
TargetContainer operator()(StringPiece line) const {
int i = 0;
StringPiece fields[sizeof...(Targets)];
// HACK(tjackson): Used for referencing fields[] corresponding to variadic
// template parameters.
auto eatField = [&]() -> StringPiece& { return fields[i++]; };
if (!split(delimiter_,
line,
detail::passthrough<StringPiece&, Targets>(eatField())...)) {
throw std::runtime_error("field count mismatch");
}
i = 0;
return TargetContainer(To<Targets>()(eatField())...);
}
};
} // namespace detail
} // namespace gen
} // namespace folly
......@@ -18,6 +18,7 @@
#define FOLLY_STRINGGEN_H_
#include "folly/Range.h"
#include "folly/experimental/Gen.h"
namespace folly {
namespace gen {
......@@ -31,6 +32,12 @@ class Unsplit;
template<class Delimiter, class OutputBuffer>
class UnsplitBuffer;
template<class TargetContainer,
class Delimiter,
class... Targets>
class SplitTo;
} // namespace detail
/**
......@@ -96,7 +103,7 @@ Unsplit unsplit(const char* delimiter) {
template<class Delimiter,
class OutputBuffer,
class UnsplitBuffer = detail::UnsplitBuffer<Delimiter, OutputBuffer>>
UnsplitBuffer unsplit(const Delimiter& delimiter, OutputBuffer* outputBuffer) {
UnsplitBuffer unsplit(Delimiter delimiter, OutputBuffer* outputBuffer) {
return UnsplitBuffer(delimiter, outputBuffer);
}
......@@ -106,6 +113,40 @@ UnsplitBuffer unsplit(const char* delimiter, OutputBuffer* outputBuffer) {
return UnsplitBuffer(delimiter, outputBuffer);
}
template<class... Targets>
detail::Map<detail::SplitTo<std::tuple<Targets...>, char, Targets...>>
eachToTuple(char delim) {
return detail::Map<
detail::SplitTo<std::tuple<Targets...>, char, Targets...>>(
detail::SplitTo<std::tuple<Targets...>, char, Targets...>(delim));
}
template<class... Targets>
detail::Map<detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>>
eachToTuple(StringPiece delim) {
return detail::Map<
detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>>(
detail::SplitTo<std::tuple<Targets...>, fbstring, Targets...>(delim));
}
template<class First, class Second>
detail::Map<detail::SplitTo<std::pair<First, Second>, char, First, Second>>
eachToPair(char delim) {
return detail::Map<
detail::SplitTo<std::pair<First, Second>, char, First, Second>>(
detail::SplitTo<std::pair<First, Second>, char, First, Second>(delim));
}
template<class First, class Second>
detail::Map<detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>>
eachToPair(StringPiece delim) {
return detail::Map<
detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>>(
detail::SplitTo<std::pair<First, Second>, fbstring, First, Second>(
to<fbstring>(delim)));
}
} // namespace gen
} // namespace folly
......
This diff is collapsed.
......@@ -1001,6 +1001,105 @@ TEST(StringGen, EmptyResplit) {
}
}
TEST(StringGen, EachToTuple) {
{
auto lines = "2:1.414:yo 3:1.732:hi";
auto actual
= split(lines, ' ')
| eachToTuple<int, double, std::string>(':')
| as<vector>();
vector<tuple<int, double, std::string>> expected {
make_tuple(2, 1.414, "yo"),
make_tuple(3, 1.732, "hi"),
};
EXPECT_EQ(expected, actual);
}
{
auto lines = "2 3";
auto actual
= split(lines, ' ')
| eachToTuple<int>(',')
| as<vector>();
vector<tuple<int>> expected {
make_tuple(2),
make_tuple(3),
};
EXPECT_EQ(expected, actual);
}
{
// StringPiece target
auto lines = "1:cat 2:dog";
auto actual
= split(lines, ' ')
| eachToTuple<int, StringPiece>(':')
| as<vector>();
vector<tuple<int, StringPiece>> expected {
make_tuple(1, "cat"),
make_tuple(2, "dog"),
};
EXPECT_EQ(expected, actual);
}
{
// Empty field
auto lines = "2:tjackson:4 3::5";
auto actual
= split(lines, ' ')
| eachToTuple<int, fbstring, int>(':')
| as<vector>();
vector<tuple<int, fbstring, int>> expected {
make_tuple(2, "tjackson", 4),
make_tuple(3, "", 5),
};
EXPECT_EQ(expected, actual);
}
{
// Excess fields
auto lines = "1:2 3:4:5";
EXPECT_THROW((split(lines, ' ')
| eachToTuple<int, int>(':')
| as<vector>()),
std::runtime_error);
}
{
// Missing fields
auto lines = "1:2:3 4:5";
EXPECT_THROW((split(lines, ' ')
| eachToTuple<int, int, int>(':')
| as<vector>()),
std::runtime_error);
}
}
TEST(StringGen, EachToPair) {
{
// char delimiters
auto lines = "2:1.414 3:1.732";
auto actual
= split(lines, ' ')
| eachToPair<int, double>(':')
| as<std::map<int, double>>();
std::map<int, double> expected {
{ 3, 1.732 },
{ 2, 1.414 },
};
EXPECT_EQ(expected, actual);
}
{
// string delimiters
auto lines = "ab=>cd ef=>gh";
auto actual
= split(lines, ' ')
| eachToPair<string, string>("=>")
| as<std::map<string, string>>();
std::map<string, string> expected {
{ "ab", "cd" },
{ "ef", "gh" },
};
EXPECT_EQ(expected, actual);
}
}
TEST(StringGen, Resplit) {
auto collect = eachTo<std::string>() | as<vector>();
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment