Add DBString class
MODIFY
CMakeLists.txt
CMakeLists.txt
@@ -35,6 +35,7 @@ add_library(lcf)
set(LCF_SOURCES
src/data.cpp
+ src/dbstring.cpp
src/encoder.cpp
src/ini.cpp
src/inireader.cpp
@@ -194,6 +195,7 @@ set(LCF_SOURCES
set(LCF_HEADERS
src/lcf/data.h
+ src/lcf/dbstring.h
src/lcf/encoder.h
src/lcf/enum_tags.h
src/lcf/flag_set.h

MODIFY
Makefile.am
Makefile.am
@@ -43,6 +43,7 @@ liblcf_la_LDFLAGS = \
-no-undefined
liblcf_la_SOURCES = \
src/data.cpp \
+ src/dbstring.cpp \
src/encoder.cpp \
src/ini.cpp \
src/inireader.cpp \
@@ -201,6 +202,7 @@ liblcf_la_SOURCES = \
lcfinclude_HEADERS = \
src/lcf/data.h \
+ src/lcf/dbstring.h \
src/lcf/encoder.h \
src/lcf/enum_tags.h \
src/lcf/flag_set.h \

MODIFY
generator/generate.py
generator/generate.py
@@ -34,6 +34,7 @@ cpp_types = {
'Int16': 'int16_t',
'Int32': 'int32_t',
'String': 'std::string',
+ 'DBString': 'DBString',
}
# Additional Jinja 2 functions
@@ -164,6 +165,9 @@ def struct_headers(ty, header_map):
if ty == 'String':
return ['<string>']
+ if ty == 'DBString':
+ return ['"lcf/dbstring.h"']
+
if ty in int_types or ty == "DatabaseVersion":
return ['<stdint.h>']

ADD
src/dbstring.cpp
+#include "lcf/dbstring.h"
+
+namespace lcf {
+
+constexpr DBString::size_type DBString::npos;
+
+static constexpr size_t AllocSize(size_t len) {
+ return sizeof(DBString::size_type) + len + 1;
+}
+
+static char* Alloc(size_t len) {
+ return reinterpret_cast<char*>(::operator new(AllocSize(len)));
+}
+
+static char* Dup(const char* other, size_t size) {
+ if (size > 0) {
+ auto* s = Alloc(size);
+ std::memcpy(s, other, AllocSize(size));
+ return s;
+ }
+ return nullptr;
+}
+
+static void Free(void* p) {
+ ::operator delete(p);
+}
+
+DBString::DBString(const char* s, size_t len)
+{
+ if (len > 0) {
+ auto* ptr = Alloc(len);
+ _storage = ptr;
+
+ *reinterpret_cast<size_type*>(ptr) = len;
+ ptr += sizeof(size_type);
+
+ std::memcpy(ptr, s, len);
+ ptr += len;
+
+ *ptr = '\0';
+ }
+}
+
+DBString::DBString(const DBString& o)
+ : _storage(Dup(o._storage, o.size()))
+{ }
+
+DBString& DBString::operator=(const DBString& o) {
+ if (this != &o) {
+ _reset();
+ _storage = Dup(o._storage, o.size());
+ }
+ return *this;
+}
+
+
+
+void DBString::_reset() noexcept {
+ Free(_storage);
+ _storage = nullptr;
+}
+
+} // namespace lcf

ADD
src/lcf/dbstring.h
+/*
+ * This file is part of liblcf. Copyright (c) 2020 liblcf authors.
+ * https://github.com/EasyRPG/liblcf - https://easyrpg.org
+ *
+ * liblcf is Free/Libre Open Source Software, released under the MIT License.
+ * For the full copyright and license information, please view the COPYING
+ * file that was distributed with this source code.
+ */
+
+#ifndef LCF_DBSTRING_H
+#define LCF_DBSTRING_H
+#include <utility>
+#include <string>
+#include <iterator>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <algorithm>
+#include <ostream>
+
+#include "lcf/string_view.h"
+
+
+namespace lcf {
+
+// A read-only string class optimized for database storage.
+class DBString {
+ public:
+ using value_type = char;
+ using char_type = value_type; // <- FIXME: HACK to workaround bug in older versions of fmtlib for Player: https://github.com/fmtlib/fmt/issues/1539
+ using traits_type = std::char_traits<char>;
+
+ using iterator = const char*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using size_type = uint32_t;
+
+ static constexpr size_type npos = size_type(-1);
+
+ constexpr DBString() = default;
+ explicit DBString(const std::string& s) : DBString(s.c_str(), s.size()) {}
+ explicit DBString(StringView s) : DBString(s.data(), s.size()) {}
+
+ // Explicit construct for general const char*
+ explicit DBString(const char* s) : DBString(s, std::strlen(s)) {}
+ // Implicit constructor to capture string literals
+ template <size_t N>
+ DBString(const char(&literal)[N]) : DBString(StringView(literal)) {}
+ DBString(const char* s, size_t len);
+
+ DBString(const DBString&);
+ DBString& operator=(const DBString&);
+ DBString(DBString&&) noexcept;
+ DBString& operator=(DBString&&) noexcept;
+
+ ~DBString() { _reset(); }
+
+ explicit operator std::string() const { return std::string(data(), size()); }
+ operator StringView() const { return StringView(data(), size()); }
+
+ char operator[](size_type i) const { return data()[i]; }
+ char front() const { return (*this)[0]; }
+ char back() const { return (*this)[size()-1]; }
+ const char* data() const;
+ const char* c_str() const { return data(); }
+
+ iterator begin() const { return data(); }
+ iterator end() const { return data() + size(); }
+
+ reverse_iterator rbegin() const { return reverse_iterator(end()); }
+ reverse_iterator rend() const { return reverse_iterator(begin()); }
+
+ bool empty() const { return size() == 0; }
+ size_type size() const;
+ private:
+ void _reset() noexcept;
+ private:
+ char* _storage = nullptr;
+};
+
+// This should be used over the conversion operator, so we can track all dbstr -> str instances
+inline std::string ToString(const DBString& s) {
+ return std::string(s);
+}
+
+#define LCF_DBSTRING_MAKE_CMP_OPS(LTYPE, RTYPE) \
+inline bool operator==(LTYPE l, RTYPE r) { return StringView(l) == StringView(r); }\
+inline bool operator!=(LTYPE l, RTYPE r) { return StringView(l) != StringView(r); }\
+inline bool operator<(LTYPE l, RTYPE r) { return StringView(l) < StringView(r); }\
+inline bool operator>(LTYPE l, RTYPE r) { return StringView(l) > StringView(r); }\
+inline bool operator<=(LTYPE l, RTYPE r) { return StringView(l) <= StringView(r); }\
+inline bool operator>=(LTYPE l, RTYPE r) { return StringView(l) >= StringView(r); }\
+
+LCF_DBSTRING_MAKE_CMP_OPS(const DBString&, const DBString&);
+LCF_DBSTRING_MAKE_CMP_OPS(StringView, const DBString&);
+LCF_DBSTRING_MAKE_CMP_OPS(const DBString&, StringView);
+LCF_DBSTRING_MAKE_CMP_OPS(const char*, const DBString&);
+LCF_DBSTRING_MAKE_CMP_OPS(const DBString&, const char*);
+
+inline std::ostream& operator<<(std::ostream& os, const DBString& s) {
+ os << StringView(s);
+ return os;
+}
+
+} // namespace lcf
+
+namespace std {
+
+template <> struct hash<lcf::DBString> {
+ size_t operator()(const lcf::DBString& s) const {
+ return std::hash<lcf::StringView>()(lcf::StringView(s));
+ }
+};
+
+} // namespace std
+
+namespace lcf {
+
+inline DBString::DBString(DBString&& o) noexcept
+ : _storage(o._storage)
+{
+ o._storage = nullptr;
+}
+
+inline DBString& DBString::operator=(DBString&& o) noexcept {
+ if (this != &o) {
+ if (_storage) {
+ _reset();
+ }
+ _storage = o._storage;
+ o._storage = nullptr;
+ }
+ return *this;
+}
+
+inline const char* DBString::data() const {
+ return _storage ? _storage + sizeof(size_type) : nullptr;
+}
+
+inline DBString::size_type DBString::size() const {
+ return _storage ? *reinterpret_cast<const size_type*>(_storage) : 0;
+}
+
+} // namespace lcf
+
+#endif

MODIFY
src/lcf/reader_lcf.h
src/lcf/reader_lcf.h
#define LCF_READER_LCF_H
#include "lcf/config.h"
+#include "lcf/dbstring.h"
#include <string>
#include <vector>
@@ -139,6 +140,7 @@ public:
* Note: The string is converted to UTF-8.
*/
void ReadString(std::string& ref, size_t size);
+ void ReadString(DBString& ref, size_t size);
/**
* Checks if the file is readable and if no error occured.

MODIFY
src/lcf/writer_lcf.h
src/lcf/writer_lcf.h
#define LCF_WRITER_LCF_H
#include "lcf/config.h"
+#include "lcf/dbstring.h"
#include <string>
#include <vector>
@@ -67,6 +68,7 @@ public:
* Note: the string is converted to the native encoding.
*/
void Write(const std::string& str);
+ void Write(const DBString& str);
/**
* Writes a compressed integer to the stream.
@@ -106,6 +108,7 @@ public:
* @return native version of string.
*/
std::string Decode(const std::string& str_to_encode);
+ std::string Decode(const DBString& str_to_encode);
private:
/** File-stream managed by this Writer. */

MODIFY
src/lcf/writer_xml.h
src/lcf/writer_xml.h
#include <memory>
#include <cstdio>
#include <stdint.h>
+#include "lcf/string_view.h"
namespace lcf {
@@ -122,6 +123,7 @@ protected:
template <class T>
void WriteVector(const std::vector<T>& val);
+ void WriteString(StringView s);
};
} //namespace lcf

MODIFY
src/reader_lcf.cpp
src/reader_lcf.cpp
@@ -190,6 +190,13 @@ void LcfReader::ReadString(std::string& ref, size_t size) {
Encode(ref);
}
+void LcfReader::ReadString(DBString& ref, size_t size) {
+ // FIXME: Relies on short string optimization
+ std::string tmp;
+ ReadString(tmp, size);
+ ref = DBString(tmp);
+}
+
bool LcfReader::IsOk() const {
return stream.good() && encoder.IsOk();
}

MODIFY
src/reader_struct.h
src/reader_struct.h
#include <cstring>
#include <cstdlib>
#include <cinttypes>
+#include "lcf/dbstring.h"
#include "lcf/reader_lcf.h"
#include "lcf/writer_lcf.h"
#include "lcf/reader_xml.h"
@@ -80,6 +81,7 @@ template <> struct TypeCategory<int32_t> { static const Category::Index val
template <> struct TypeCategory<bool> { static const Category::Index value = Category::Primitive; };
template <> struct TypeCategory<double> { static const Category::Index value = Category::Primitive; };
template <> struct TypeCategory<std::string> { static const Category::Index value = Category::Primitive; };
+template <> struct TypeCategory<DBString> { static const Category::Index value = Category::Primitive; };
template <class T>
struct TypeCategory<std::vector<T>> {
@@ -285,6 +287,31 @@ struct Primitive<std::string> {
}
};
+/**
+ * DBString specialization.
+ */
+template <>
+struct Primitive<DBString> {
+ static void ReadLcf(DBString& ref, LcfReader& stream, uint32_t length) {
+ stream.ReadString(ref, length);
+#ifdef LCF_DEBUG_TRACE
+ printf(" %s\n", ref.c_str());
+#endif
+ }
+ static void WriteLcf(const DBString& ref, LcfWriter& stream) {
+ stream.Write(ref);
+ }
+ static int LcfSize(const DBString& ref, LcfWriter& stream) {
+ return stream.Decode(ref).size();
+ }
+ static void WriteXml(const DBString& ref, XmlWriter& stream) {
+ stream.Write(ref);
+ }
+ static void ParseXml(DBString& ref, const std::string& data) {
+ XmlReader::Read(ref, data);
+ }
+};
+
/**
* Primitive Reader.
*/

MODIFY
src/reader_xml.cpp
src/reader_xml.cpp
#include <cstdarg>
#include "lcf/reader_lcf.h"
#include "lcf/reader_xml.h"
+#include "lcf/dbstring.h"
// Expat callbacks
#if LCF_SUPPORT_XML
@@ -180,6 +181,13 @@ void XmlReader::Read<std::string>(std::string& val, const std::string& data) {
}
}
+template <>
+void XmlReader::Read<DBString>(DBString& val, const std::string& data) {
+ std::string sval;
+ Read(sval, data);
+ val = DBString(sval);
+}
+
template <class T>
void XmlReader::ReadVector(std::vector<T>& val, const std::string& data) {
val.clear();

MODIFY
src/writer_lcf.cpp
src/writer_lcf.cpp
@@ -120,6 +120,13 @@ void LcfWriter::Write(const std::string& _str) {
}
}
+void LcfWriter::Write(const DBString& _str) {
+ std::string str = Decode(_str);
+ if (!str.empty()) {
+ Write(&*str.begin(), 1, str.size());
+ }
+}
+
uint32_t LcfWriter::Tell() {
return (uint32_t)stream.tellp();
}
@@ -134,6 +141,11 @@ std::string LcfWriter::Decode(const std::string& str) {
return copy;
}
+std::string LcfWriter::Decode(const DBString& str) {
+ // FIXME: String copy here
+ return Decode(str.to_string());
+}
+
#ifdef WORDS_BIGENDIAN
void LcfWriter::SwapByteOrder(uint16_t& us)
{

MODIFY
src/writer_xml.cpp
src/writer_xml.cpp
#include <vector>
#include "lcf/writer_xml.h"
+#include "lcf/dbstring.h"
namespace lcf {
@@ -65,12 +66,9 @@ void XmlWriter::Write<double>(const double& val) {
stream << val;
}
-template <>
-void XmlWriter::Write<std::string>(const std::string& val) {
+void XmlWriter::WriteString(StringView val) {
Indent();
- std::string::const_iterator it;
- for (it = val.begin(); it != val.end(); it++) {
- int c = (int) *it;
+ for (auto c: val) {
switch (c) {
case '<':
stream << "&lt;";
@@ -103,6 +101,16 @@ void XmlWriter::Write<std::string>(const std::string& val) {
}
}
+template <>
+void XmlWriter::Write<std::string>(const std::string& val) {
+ WriteString(val);
+}
+
+template <>
+void XmlWriter::Write<DBString>(const DBString& val) {
+ WriteString(val);
+}
+
template <>
void XmlWriter::Write<std::vector<int32_t>>(const std::vector<int32_t>& val) {
WriteVector<int32_t>(val);
@@ -206,6 +214,7 @@ template void XmlWriter::WriteNode<uint32_t>(const std::string& name, const uint
template void XmlWriter::WriteNode<int32_t>(const std::string& name, const int32_t& val);
template void XmlWriter::WriteNode<double>(const std::string& name, const double& val);
template void XmlWriter::WriteNode<std::string>(const std::string& name, const std::string& val);
+template void XmlWriter::WriteNode<DBString>(const std::string& name, const DBString& val);
template void XmlWriter::WriteNode<std::vector<bool>>(const std::string& name, const std::vector<bool>& val);
template void XmlWriter::WriteNode<std::vector<uint8_t>>(const std::string& name, const std::vector<uint8_t>& val);

ADD
tests/dbstring.cpp
+/*
+ * This file is part of liblcf. Copyright (c) 2020 liblcf authors.
+ * https://github.com/EasyRPG/liblcf - https://easyrpg.org
+ *
+ * liblcf is Free/Libre Open Source Software, released under the MIT License.
+ * For the full copyright and license information, please view the COPYING
+ * file that was distributed with this source code.
+ */
+
+#include "lcf/dbstring.h"
+#include "doctest.h"
+
+using namespace lcf;
+
+TEST_SUITE_BEGIN("DBString");
+
+TEST_CASE("ConstructDef") {
+ DBString x;
+
+ REQUIRE(x.empty());
+ REQUIRE_EQ(x.size(), 0);
+ REQUIRE_EQ(x, "");
+}
+
+template <typename T>
+void testConstruct(const T& init) {
+}
+
+TEST_CASE_TEMPLATE("Construct", T, StringView, std::string, char*, DBString) {
+ DBString x(T("abc"));
+
+ REQUIRE_FALSE(x.empty());
+ REQUIRE_EQ(x.size(), 3);
+ REQUIRE_EQ(x, "abc");
+
+ REQUIRE_EQ(x.front(), 'a');
+ REQUIRE_EQ(x[0], 'a');
+ REQUIRE_EQ(x[1], 'b');
+ REQUIRE_EQ(x[2], 'c');
+ REQUIRE_EQ(x.back(), 'c');
+
+ REQUIRE_EQ(x.data()[0], 'a');
+ REQUIRE_EQ(x.data()[1], 'b');
+ REQUIRE_EQ(x.data()[2], 'c');
+
+ REQUIRE_EQ(x.c_str()[0], 'a');
+ REQUIRE_EQ(x.c_str()[1], 'b');
+ REQUIRE_EQ(x.c_str()[2], 'c');
+ REQUIRE_EQ(x.c_str()[3], '\0');
+
+ {
+ auto iter = x.begin();
+ REQUIRE_EQ(*(iter++), 'a');
+ REQUIRE_EQ(*(iter++), 'b');
+ REQUIRE_EQ(*(iter++), 'c');
+ REQUIRE_EQ(iter, x.end());
+ }
+
+ {
+ auto iter = x.rbegin();
+ REQUIRE_EQ(*(iter++), 'c');
+ REQUIRE_EQ(*(iter++), 'b');
+ REQUIRE_EQ(*(iter++), 'a');
+ REQUIRE_EQ(iter, x.rend());
+ }
+}
+
+TEST_CASE("Construct2") {
+ DBString x("abc", 2);
+
+ REQUIRE_FALSE(x.empty());
+ REQUIRE_EQ(x.size(), 2);
+ REQUIRE_EQ(x, "ab");
+}
+
+TEST_CASE("Swap") {
+ DBString a("a");
+ DBString b("b");
+ a.swap(b);
+
+ REQUIRE_EQ(a, "b");
+ REQUIRE_EQ(b, "a");
+}
+
+TEST_CASE("Cmp") {
+ DBString a("a");
+ DBString b("b");
+
+ REQUIRE(a == a);
+ REQUIRE_FALSE(a != a);
+ REQUIRE_FALSE(a < a);
+ REQUIRE(a <= a);
+ REQUIRE_FALSE(a > a);
+ REQUIRE(a >= a);
+
+ REQUIRE_FALSE(a == b);
+ REQUIRE(a != b);
+ REQUIRE(a < b);
+ REQUIRE(a <= b);
+ REQUIRE_FALSE(a > b);
+ REQUIRE_FALSE(a >= b);
+
+ REQUIRE_FALSE(b == a);
+ REQUIRE(b != a);
+ REQUIRE_FALSE(b < a);
+ REQUIRE_FALSE(b <= a);
+ REQUIRE(b > a);
+ REQUIRE(b >= a);
+}
+
+
+TEST_CASE("Copy") {
+ DBString a("a");
+ DBString b("b");
+
+ b = a;
+ REQUIRE_EQ(a, "a");
+ REQUIRE_EQ(b, "a");
+
+ b = a;
+ REQUIRE_EQ(a, "a");
+ REQUIRE_EQ(b, "a");
+
+ b = b;
+ REQUIRE_EQ(a, "a");
+ REQUIRE_EQ(b, "a");
+
+ DBString c(b);
+ REQUIRE_EQ(a, "a");
+ REQUIRE_EQ(b, "a");
+ REQUIRE_EQ(c, "a");
+}
+
+TEST_CASE("Move") {
+ DBString a("a");
+ DBString b("b");
+
+ b = std::move(a);
+ REQUIRE_EQ(a, "");
+ REQUIRE_EQ(b, "a");
+
+ b = std::move(b);
+ REQUIRE_EQ(a, "");
+ REQUIRE_EQ(b, "a");
+
+ DBString c(std::move(b));
+ REQUIRE_EQ(a, "");
+ REQUIRE_EQ(b, "");
+ REQUIRE_EQ(c, "a");
+
+ c = std::move(b);
+ REQUIRE_EQ(a, "");
+ REQUIRE_EQ(b, "");
+ REQUIRE_EQ(c, "");
+}
+
+TEST_SUITE_END();

Add DBString class