Skip to content

Commit f6b178d

Browse files
committed
Add compactStringSerializer
1 parent e40866a commit f6b178d

File tree

3 files changed

+524
-0
lines changed

3 files changed

+524
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#ifndef FWCore_Utilities_interface_compactStringSerializer_h
2+
#define FWCore_Utilities_interface_compactStringSerializer_h
3+
4+
#include <cassert>
5+
#include <iterator>
6+
#include <numeric>
7+
#include <ranges>
8+
#include <string>
9+
#include <string_view>
10+
11+
namespace edm::compactString {
12+
namespace detail {
13+
constexpr std::string_view kDelimiters = "\x1d\x1e";
14+
constexpr char kContainerDelimiter = kDelimiters[0]; // "group separator" in ASCII
15+
constexpr char kElementDelimiter = kDelimiters[1]; // "record separator" in ASCII
16+
17+
void throwIfContainsDelimiters(std::string const& str);
18+
} // namespace detail
19+
20+
/**
21+
* Following three functions serialize a sequence of strings and containers of strings
22+
*
23+
* Each top-level string or container of strings is separated with kContainerDelimeter
24+
* In case of container of strings, each element is separated with kElementDelimeter
25+
* The serialized string will end with kContainerDelimeter and a null character
26+
*
27+
* The functions throw an exception if the serialized strings
28+
* contain any of the delimeter characters. The underlying string
29+
* operations may also throw exceptions.
30+
*/
31+
inline std::string serialize(std::string arg) noexcept(false) {
32+
detail::throwIfContainsDelimiters(arg);
33+
arg += detail::kContainerDelimiter;
34+
return arg;
35+
}
36+
37+
template <typename R>
38+
requires std::ranges::input_range<R> and std::is_same_v<std::ranges::range_value_t<R>, std::string>
39+
std::string serialize(R const& arg) noexcept(false) {
40+
std::string ret;
41+
42+
if (not std::ranges::empty(arg)) {
43+
for (std::string const& elem : arg) {
44+
ret.reserve(ret.size() + elem.size() + 1);
45+
detail::throwIfContainsDelimiters(elem);
46+
ret += elem;
47+
ret += detail::kElementDelimiter;
48+
}
49+
}
50+
51+
ret += detail::kContainerDelimiter;
52+
return ret;
53+
}
54+
55+
template <typename T, typename... Args>
56+
requires(sizeof...(Args) >= 1)
57+
std::string serialize(T&& arg0, Args&&... args) noexcept(false) {
58+
return serialize(std::forward<T>(arg0)) + serialize(std::forward<Args>(args)...);
59+
}
60+
61+
/**
62+
* Following three functions deserialize a string 'input' into a
63+
* sequence of strings and containers of strings
64+
*
65+
* The 'input' string is assumed to be serialized with the
66+
* serialize() functions above.
67+
*
68+
* The output arguments following the 'input' define the schema of
69+
* the deserialization.
70+
* - std::string& for strings
71+
* - output iterator for containers of strings (e.g. std::back_inserter(vector))
72+
*
73+
* Upon success, the return value is the position in `input` for the
74+
* next possible element (i.e. the position after the
75+
* kContainerDelimiter), that is also the number of characters
76+
* consumed by the deserializatiom..
77+
*
78+
* Upon failure, returns 0 to denote the beginning of `input`. The
79+
* output arguments may have been modified.
80+
*
81+
* The functions do not explicitly throw exceptions, but underlying
82+
* operations may throw exceptions.
83+
*/
84+
inline std::string_view::size_type deserialize(std::string_view input, std::string& arg) {
85+
auto const pos = input.find_first_of(detail::kDelimiters);
86+
if (pos == std::string_view::npos or input[pos] != detail::kContainerDelimiter) {
87+
return 0;
88+
}
89+
arg = input.substr(0, pos);
90+
return pos + 1; // skip delimiter
91+
}
92+
93+
template <std::output_iterator<std::string> I>
94+
inline std::string_view::size_type deserialize(std::string_view input, I arg) {
95+
auto pos = input.find_first_of(detail::kDelimiters);
96+
// invalid input
97+
if (pos == std::string_view::npos) {
98+
return 0;
99+
}
100+
// no elements
101+
if (input[pos] == detail::kContainerDelimiter) {
102+
// invalid input for empty container
103+
if (pos != 0) {
104+
return 0;
105+
}
106+
// skip delimiter
107+
return pos + 1;
108+
}
109+
110+
std::string_view::size_type prev = 0;
111+
while (pos != std::string_view::npos and input[pos] == detail::kElementDelimiter) {
112+
*arg = std::string(input.substr(prev, pos - prev));
113+
++arg;
114+
prev = pos + 1; //skip delimiter
115+
pos = input.find_first_of(detail::kDelimiters, prev);
116+
}
117+
118+
// every container must end with kContainerDelimiter
119+
// reaching npos is an error
120+
if (pos == std::string_view::npos) {
121+
return 0;
122+
}
123+
assert(input[pos] == detail::kContainerDelimiter);
124+
125+
return pos + 1; // skip delimiter
126+
}
127+
128+
template <typename T, typename... Args>
129+
requires(sizeof...(Args) >= 1)
130+
std::string_view::size_type deserialize(std::string_view input, T&& arg0, Args&&... args) {
131+
auto pos = deserialize(input, std::forward<T>(arg0));
132+
if (pos != 0) {
133+
auto const ret = deserialize(input.substr(pos), std::forward<Args>(args)...);
134+
pos = (ret == 0) ? 0 : pos + ret;
135+
}
136+
return pos;
137+
}
138+
} // namespace edm::compactString
139+
140+
#endif
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "FWCore/Utilities/interface/compactStringSerializer.h"
2+
#include "FWCore/Utilities/interface/Exception.h"
3+
4+
namespace edm::compactString::detail {
5+
void throwIfContainsDelimiters(std::string const& str) {
6+
auto pos = str.find_first_of(kDelimiters);
7+
if (pos != std::string::npos) {
8+
cms::Exception ex("compactString");
9+
ex << "Serialized string '" << str << "' contains ";
10+
if (str[pos] == kContainerDelimiter) {
11+
ex << "container";
12+
} else {
13+
ex << "element";
14+
}
15+
ex << " delimiter at position " << pos;
16+
throw ex;
17+
}
18+
}
19+
} // namespace edm::compactString::detail

0 commit comments

Comments
 (0)