Commit ebdad7a4 authored by Kenton Varda's avatar Kenton Varda

Add kj::strPreallocated() which writes into a pre-allocated string.

This is useful especially in signal handlers where malloc() is potentially unsafe.
parent 30c7d54b
......@@ -191,6 +191,23 @@ KJ_TEST("string literals with _kj suffix") {
KJ_EXPECT(kj::str(ARR) == "foo");
}
KJ_TEST("kj::delimited() and kj::strPreallocated()") {
KJ_EXPECT(str(delimited(arr(1, 23, 456, 78), "::")) == "1::23::456::78");
{
char buffer[256];
KJ_EXPECT(strPreallocated(buffer, delimited(arr(1, 23, 456, 78), "::"), 'x')
== "1::23::456::78x");
KJ_EXPECT(strPreallocated(buffer, "foo", 123, true) == "foo123true");
}
{
char buffer[5];
KJ_EXPECT(strPreallocated(buffer, delimited(arr(1, 23, 456, 78), "::"), 'x') == "1::2");
KJ_EXPECT(strPreallocated(buffer, "foo", 123, true) == "foo1");
}
}
} // namespace
} // namespace _ (private)
} // namespace kj
......@@ -53,11 +53,21 @@ String StringTree::flatten() const {
return result;
}
void StringTree::flattenTo(char* __restrict__ target) const {
char* StringTree::flattenTo(char* __restrict__ target) const {
visit([&target](ArrayPtr<const char> text) {
memcpy(target, text.begin(), text.size());
target += text.size();
});
return target;
}
char* StringTree::flattenTo(char* __restrict__ target, char* limit) const {
visit([&target,limit](ArrayPtr<const char> text) {
size_t size = kj::min(text.size(), limit - target);
memcpy(target, text.begin(), size);
target += size;
});
return target;
}
} // namespace kj
......@@ -62,8 +62,10 @@ public:
// TODO(someday): flatten() when *this is an rvalue and when branches.size() == 0 could simply
// return `kj::mv(text)`. Requires reference qualifiers (Clang 3.3 / GCC 4.8).
void flattenTo(char* __restrict__ target) const;
// Copy the contents to the given character array. Does not add a NUL terminator.
char* flattenTo(char* __restrict__ target) const;
char* flattenTo(char* __restrict__ target, char* limit) const;
// Copy the contents to the given character array. Does not add a NUL terminator. Returns a
// pointer just past the end of what was filled.
private:
size_t size_;
......@@ -124,6 +126,14 @@ char* fill(char* __restrict__ target, const StringTree& first, Rest&&... rest) {
return fill(target + first.size(), kj::fwd<Rest>(rest)...);
}
template <typename... Rest>
char* fillLimited(char* __restrict__ target, char* limit, const StringTree& first, Rest&&... rest) {
// Make str() work with stringifiers that return StringTree by patching fill().
target = first.flattenTo(target, limit);
return fillLimited(target + first.size(), limit, kj::fwd<Rest>(rest)...);
}
template <typename T> constexpr bool isStringTree() { return false; }
template <> constexpr bool isStringTree<StringTree>() { return true; }
......
......@@ -264,9 +264,12 @@ inline size_t sum(std::initializer_list<size_t> nums) {
}
inline char* fill(char* ptr) { return ptr; }
inline char* fillLimited(char* ptr, char* limit) { return ptr; }
template <typename... Rest>
char* fill(char* __restrict__ target, const StringTree& first, Rest&&... rest);
template <typename... Rest>
char* fillLimited(char* __restrict__ target, char* limit, const StringTree& first, Rest&&... rest);
// Make str() work with stringifiers that return StringTree by patching fill().
//
// Defined in string-tree.h.
......@@ -295,6 +298,27 @@ inline String concat(String&& arr) {
return kj::mv(arr);
}
template <typename First, typename... Rest>
char* fillLimited(char* __restrict__ target, char* limit, const First& first, Rest&&... rest) {
auto i = first.begin();
auto end = first.end();
while (i != end) {
if (target == limit) return target;
*target++ = *i++;
}
return fillLimited(target, limit, kj::fwd<Rest>(rest)...);
}
template <typename T>
class Delimited;
// Delimits a sequence of type T with a string delimiter. Implements kj::delimited().
template <typename T, typename... Rest>
char* fill(char* __restrict__ target, Delimited<T> first, Rest&&... rest);
template <typename T, typename... Rest>
char* fillLimited(char* __restrict__ target, char* limit, Delimited<T> first,Rest&&... rest);
// As with StringTree, we special-case Delimited<T>.
struct Stringifier {
// This is a dummy type with only one instance: STR (below). To make an arbitrary type
// stringifiable, define `operator*(Stringifier, T)` to return an iterable container of `char`.
......@@ -393,6 +417,10 @@ String str(Params&&... params) {
inline String str(String&& s) { return mv(s); }
// Overload to prevent redundant allocation.
template <typename T>
_::Delimited<T> delimited(T&& arr, kj::StringPtr delim);
// Use to stringify an array.
template <typename T>
String strArray(T&& arr, const char* delim) {
size_t delimLen = strlen(delim);
......@@ -416,6 +444,29 @@ String strArray(T&& arr, const char* delim) {
return result;
}
template <typename... Params>
StringPtr strPreallocated(ArrayPtr<char> buffer, Params&&... params) {
// Like str() but writes into a preallocated buffer. If the buffer is not long enough, the result
// is truncated (but still NUL-terminated).
//
// This can be used like:
//
// char buffer[256];
// StringPtr text = strPreallocated(buffer, params...);
//
// This is useful for optimization. It can also potentially be used safely in async signal
// handlers. HOWEVER, to use in an async signal handler, all of the stringifiers for the inputs
// must also be signal-safe. KJ guarantees signal safety when stringifying any built-in integer
// type, basic char/byte sequences (ArrayPtr<byte>, String, etc.), as well as Array<T> as long
// as T can also be stringified safely. To safely stringify a delimited array, you must use
// kj::delimited(arr, delim) rather than the deprecated kj::strArray(arr, delim).
char* end = _::fillLimited(buffer.begin(), buffer.end() - 1,
toCharSequence(kj::fwd<Params>(params))...);
*end = '\0';
return StringPtr(buffer.begin(), end);
}
namespace _ { // private
template <typename T>
......@@ -548,6 +599,101 @@ inline String heapString(ArrayPtr<const char> value) {
return heapString(value.begin(), value.size());
}
namespace _ { // private
template <typename T>
class Delimited {
public:
Delimited(T array, kj::StringPtr delimiter)
: array(kj::fwd<T>(array)), delimiter(delimiter) {}
// TODO(someday): In theory we should support iteration as a character sequence, but the iterator
// will be pretty complicated.
size_t size() {
ensureStringifiedInitialized();
size_t result = 0;
bool first = true;
for (auto& e: stringified) {
if (first) {
first = false;
} else {
result += delimiter.size();
}
result += e.size();
}
return result;
}
char* flattenTo(char* __restrict__ target) {
ensureStringifiedInitialized();
bool first = true;
for (auto& elem: stringified) {
if (first) {
first = false;
} else {
target = fill(target, delimiter);
}
target = fill(target, elem);
}
return target;
}
char* flattenTo(char* __restrict__ target, char* limit) {
// This is called in the strPreallocated(). We want to avoid allocation. size() will not have
// been called in this case, so hopefully `stringified` is still uninitialized. We will
// stringify each item and immediately use it.
bool first = true;
for (auto&& elem: array) {
if (target == limit) return target;
if (first) {
first = false;
} else {
target = fillLimited(target, limit, delimiter);
}
target = fillLimited(target, limit, kj::toCharSequence(elem));
}
return target;
}
private:
typedef decltype(toCharSequence(*instance<T>().begin())) StringifiedItem;
T array;
kj::StringPtr delimiter;
Array<StringifiedItem> stringified;
void ensureStringifiedInitialized() {
if (array.size() > 0 && stringified.size() == 0) {
stringified = KJ_MAP(e, array) { return toCharSequence(e); };
}
}
};
template <typename T, typename... Rest>
char* fill(char* __restrict__ target, Delimited<T> first, Rest&&... rest) {
target = first.flattenTo(target);
return fill(target, kj::fwd<Rest>(rest)...);
}
template <typename T, typename... Rest>
char* fillLimited(char* __restrict__ target, char* limit, Delimited<T> first, Rest&&... rest) {
target = first.flattenTo(target, limit);
return fillLimited(target, limit, kj::fwd<Rest>(rest)...);
}
template <typename T>
inline Delimited<T>&& KJ_STRINGIFY(Delimited<T>&& delimited) { return kj::mv(delimited); }
template <typename T>
inline const Delimited<T>& KJ_STRINGIFY(const Delimited<T>& delimited) { return delimited; }
} // namespace _ (private)
template <typename T>
_::Delimited<T> delimited(T&& arr, kj::StringPtr delim) {
return _::Delimited<T>(kj::fwd<T>(arr), delim);
}
} // namespace kj
constexpr kj::StringPtr operator "" _kj(const char* str, size_t n) {
......
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