Skip to content

Commit

Permalink
fix(kvpp): preallocate storage for strings
Browse files Browse the repository at this point in the history
  • Loading branch information
craftablescience committed Jun 18, 2024
1 parent 70be509 commit c321f28
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 32 deletions.
14 changes: 8 additions & 6 deletions include/kvpp/kvpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <sourcepp/math/Integer.h>

class BufferStream;
class BufferStreamReadOnly;

namespace kvpp {

Expand All @@ -28,9 +29,9 @@ class Element {
} else if constexpr (std::same_as<T, bool>) {
return static_cast<bool>(this->getValue<int>());
} else if constexpr (std::same_as<T, int>) {
return std::stoi(this->value);
return std::stoi(std::string{this->value});
} else if constexpr (std::same_as<T, float>) {
return std::stof(this->value);
return std::stof(std::string{this->value});
}
return T{};
}
Expand Down Expand Up @@ -63,16 +64,16 @@ class Element {
[[nodiscard]] bool isInvalid() const;

protected:
std::string key;
std::string value;
std::string conditional;
std::string_view key;
std::string_view value = ""; // NOLINT(*-redundant-string-init)
std::string_view conditional = ""; // NOLINT(*-redundant-string-init)
std::vector<Element> children;

Element() = default;

static const Element& getInvalid();

static void readElementsFrom(BufferStream& stream, std::vector<Element>& element, bool useEscapeSequences);
static void readElements(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<Element>& element, bool useEscapeSequences);
};

class KV1 : public Element {
Expand All @@ -84,6 +85,7 @@ class KV1 : public Element {
using Element::getValue;
using Element::getConditional;

std::string backingData;
bool useEscapeSequences;
};

Expand Down
52 changes: 29 additions & 23 deletions src/kvpp/kvpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ void eatWhitespace(BufferStream& stream) {
while (::isWhitespace(stream.read<char>())) {}
stream.seek(-1, std::ios::cur);

if (stream.peek<char>(0) == '/' && (stream.peek<char>(1) == '/' || stream.peek<char>(1) == '*')) {
if (stream.peek<char>(0) == '/' && stream.peek<char>(1) == '/') {
stream.skip(2);
::eatComment(stream);
::eatWhitespace(stream);
return;
}
}

std::string readString(BufferStream& stream, bool useEscapeSequences, char start = '\"', char end = '\"') {
std::string out;
std::string_view readString(BufferStreamReadOnly& stream, BufferStream& backing, bool useEscapeSequences, char start = '\"', char end = '\"') {
auto startSpan = backing.tell();

bool stopAtWhitespace = true;
char c = stream.read<char>();
if (c == start) {
stopAtWhitespace = false;
} else {
out += c;
backing << c;
}

for (c = stream.read<char>(); c != end; c = stream.read<char>()) {
Expand All @@ -52,21 +52,21 @@ std::string readString(BufferStream& stream, bool useEscapeSequences, char start
break;
}
if (n == 'n') {
out += '\n';
backing << '\n';
} else if (n == 't') {
out += '\t';
backing << '\t';
} else if (n == '\\' || n == '\"') {
out += n;
backing << n;
} else {
out += c;
out += n;
backing << c << n;
}
} else {
out += c;
backing << c;
}
}

return out;
backing << '\0';
return {reinterpret_cast<const char*>(backing.data()) + startSpan, backing.tell() - 1 - startSpan};
}

} // namespace
Expand Down Expand Up @@ -142,33 +142,37 @@ const Element& Element::getInvalid() {
}

// NOLINTNEXTLINE(*-no-recursion)
void Element::readElementsFrom(BufferStream& stream, std::vector<Element>& elements, bool useEscapeSequences) {
void Element::readElements(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<Element>& elements, bool useEscapeSequences) {
while (true) {
// Check if the block is over
::eatWhitespace(stream);
if (static_cast<char>(stream.peek(0)) == '}') {
stream.skip();
break;
}

auto childKey = ::readString(stream, useEscapeSequences);
elements.push_back({});
auto& element = elements.back();
element.key = childKey;
::eatWhitespace(stream);

// Read key
{
auto childKey = ::readString(stream, backing, useEscapeSequences);
elements.push_back({});
elements.back().key = childKey;
::eatWhitespace(stream);
}
// Read value
if (stream.peek<char>(0) != '{') {
element.value = ::readString(stream, useEscapeSequences);
elements.back().value = ::readString(stream, backing, useEscapeSequences);
::eatWhitespace(stream);
}
// Read conditional
if (stream.peek<char>(0) == '[') {
element.conditional = ::readString(stream, useEscapeSequences, '[', ']');
elements.back().conditional = ::readString(stream, backing, useEscapeSequences, '[', ']');
::eatWhitespace(stream);
}
// Read block
if (stream.peek<char>(0) == '{') {
stream.skip();
::eatWhitespace(stream);
if (stream.peek<char>(0) != '}') {
readElementsFrom(stream, element.children, useEscapeSequences);
readElements(stream, backing, elements.back().children, useEscapeSequences);
} else {
stream.skip();
}
Expand All @@ -180,7 +184,9 @@ KV1::KV1(std::string_view kv1Data, bool useEscapeSequences_)
: Element()
, useEscapeSequences(useEscapeSequences_) {
BufferStreamReadOnly stream{kv1Data.data(), kv1Data.size()};
this->backingData.resize(kv1Data.size());
BufferStream backing{this->backingData};
try {
readElementsFrom(stream, this->children, this->useEscapeSequences);
readElements(stream, backing, this->children, this->useEscapeSequences);
} catch (const std::overflow_error&) {}
}
2 changes: 1 addition & 1 deletion src/thirdparty/bufferstream
2 changes: 0 additions & 2 deletions test/kvpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,8 @@ TEST(kvpp, read_escaped) {

TEST(kvpp, read_comments) {
KV1 kv1{R"(
/* keys */
"keys"
{ // cool
/* test */ "i'm not parsed" "because keyvalues is a bad format"
"test" "1" // so nice
"test 2" 0 // here's another one
}
Expand Down

0 comments on commit c321f28

Please sign in to comment.