From ffc117fce4bfe2d995b1c0197f6105e309cc3e52 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Mon, 17 Jun 2024 20:26:46 -0400 Subject: [PATCH] fix(kvpp): preallocate storage for strings --- include/kvpp/kvpp.h | 14 ++++++------ src/kvpp/kvpp.cpp | 52 +++++++++++++++++++++++++-------------------- test/kvpp.cpp | 2 -- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/include/kvpp/kvpp.h b/include/kvpp/kvpp.h index c735bab89..1ff8c98be 100644 --- a/include/kvpp/kvpp.h +++ b/include/kvpp/kvpp.h @@ -8,6 +8,7 @@ #include class BufferStream; +class BufferStreamReadOnly; namespace kvpp { @@ -28,9 +29,9 @@ class Element { } else if constexpr (std::same_as) { return static_cast(this->getValue()); } else if constexpr (std::same_as) { - return std::stoi(this->value); + return std::stoi(std::string{this->value}); } else if constexpr (std::same_as) { - return std::stof(this->value); + return std::stof(std::string{this->value}); } return T{}; } @@ -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 children; Element() = default; static const Element& getInvalid(); - static void readElementsFrom(BufferStream& stream, std::vector& element, bool useEscapeSequences); + static void readElements(BufferStreamReadOnly& stream, BufferStream& backing, std::vector& element, bool useEscapeSequences); }; class KV1 : public Element { @@ -84,6 +85,7 @@ class KV1 : public Element { using Element::getValue; using Element::getConditional; + std::string backingData; bool useEscapeSequences; }; diff --git a/src/kvpp/kvpp.cpp b/src/kvpp/kvpp.cpp index 4eeb7a325..b1103de7e 100644 --- a/src/kvpp/kvpp.cpp +++ b/src/kvpp/kvpp.cpp @@ -23,7 +23,7 @@ void eatWhitespace(BufferStream& stream) { while (::isWhitespace(stream.read())) {} stream.seek(-1, std::ios::cur); - if (stream.peek(0) == '/' && (stream.peek(1) == '/' || stream.peek(1) == '*')) { + if (stream.peek(0) == '/' && stream.peek(1) == '/') { stream.skip(2); ::eatComment(stream); ::eatWhitespace(stream); @@ -31,15 +31,15 @@ void eatWhitespace(BufferStream& stream) { } } -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(); if (c == start) { stopAtWhitespace = false; } else { - out += c; + backing << c; } for (c = stream.read(); c != end; c = stream.read()) { @@ -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(backing.data()) + startSpan, backing.tell() - 1 - startSpan}; } } // namespace @@ -142,33 +142,37 @@ const Element& Element::getInvalid() { } // NOLINTNEXTLINE(*-no-recursion) -void Element::readElementsFrom(BufferStream& stream, std::vector& elements, bool useEscapeSequences) { +void Element::readElements(BufferStreamReadOnly& stream, BufferStream& backing, std::vector& elements, bool useEscapeSequences) { while (true) { + // Check if the block is over ::eatWhitespace(stream); if (static_cast(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(0) != '{') { - element.value = ::readString(stream, useEscapeSequences); + elements.back().value = ::readString(stream, backing, useEscapeSequences); ::eatWhitespace(stream); } + // Read conditional if (stream.peek(0) == '[') { - element.conditional = ::readString(stream, useEscapeSequences, '[', ']'); + elements.back().conditional = ::readString(stream, backing, useEscapeSequences, '[', ']'); ::eatWhitespace(stream); } + // Read block if (stream.peek(0) == '{') { stream.skip(); ::eatWhitespace(stream); if (stream.peek(0) != '}') { - readElementsFrom(stream, element.children, useEscapeSequences); + readElements(stream, backing, elements.back().children, useEscapeSequences); } else { stream.skip(); } @@ -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&) {} } diff --git a/test/kvpp.cpp b/test/kvpp.cpp index 00c0e66d9..5df1783ea 100644 --- a/test/kvpp.cpp +++ b/test/kvpp.cpp @@ -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 }