diff --git a/src/remote.cc b/src/remote.cc index ca9e987cc..e2756ef1c 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -164,7 +164,7 @@ String read(int socket) String res; if (length > 0) { - res.resize((int)length); + res.force_size((int)length); read(socket, &res[0_byte], (int)length); } return res; diff --git a/src/string.cc b/src/string.cc index 9395a26e7..2734042b6 100644 --- a/src/string.cc +++ b/src/string.cc @@ -10,6 +10,125 @@ namespace Kakoune { +String::Data::Data(const char* data, size_t size, size_t capacity) +{ + if (capacity > Short::capacity) + { + if (capacity & 1) + ++capacity; + + l.ptr = Alloc{}.allocate(capacity+1); + l.size = size; + l.capacity = capacity; + + memcpy(l.ptr, data, size); + l.ptr[size] = 0; + } + else + set_short(data, size); +} + +String::Data::Data(Data&& other) noexcept +{ + if (other.is_long()) + { + l = other.l; + other.s.size = 1; + } + else + s = other.s; +} + +String::Data& String::Data::operator=(const Data& other) +{ + const size_t new_size = other.size(); + reserve(new_size); + memcpy(data(), other.data(), new_size+1); + set_size(new_size); + + return *this; +} + +String::Data& String::Data::operator=(Data&& other) noexcept +{ + if (other.is_long()) + { + l = other.l; + other.set_empty(); + } + else + s = other.s; + + return *this; +} + +template +void String::Data::reserve(size_t new_capacity) +{ + if (new_capacity <= capacity()) + return; + + if (is_long()) + new_capacity = std::max(l.capacity * 2, new_capacity); + + char* new_ptr = Alloc{}.allocate(new_capacity+1); + if (copy) + { + memcpy(new_ptr, data(), size()+1); + l.size = size(); + } + release(); + + l.ptr = new_ptr; + l.capacity = new_capacity; +} + +template void String::Data::reserve(size_t); +template void String::Data::reserve(size_t); + +void String::Data::force_size(size_t new_size) +{ + reserve(new_size); + set_size(new_size); +} + +void String::Data::append(const char* str, size_t len) +{ + const size_t new_size = size() + len; + reserve(new_size); + + memcpy(data() + size(), str, len); + set_size(new_size); + data()[new_size] = 0; +} + +void String::Data::clear() +{ + release(); + set_empty(); +} + +void String::Data::release() +{ + if (is_long()) + Alloc{}.deallocate(l.ptr, l.capacity+1); +} + +void String::Data::set_size(size_t size) +{ + if (is_long()) + l.size = size; + else + s.size = (size << 1) | 1; +} + +void String::Data::set_short(const char* data, size_t size) +{ + s.size = (size << 1) | 1; + memcpy(s.string, data, size); + s.string[size] = 0; +} + const String String::ms_empty; Vector split(StringView str, char separator, char escape) diff --git a/src/string.hh b/src/string.hh index 638f30f99..ab83b56a2 100644 --- a/src/string.hh +++ b/src/string.hh @@ -8,7 +8,7 @@ #include "utf8.hh" #include "vector.hh" -#include +#include #include namespace Kakoune @@ -94,9 +94,6 @@ constexpr ByteCount strlen(const char* s) class String : public StringOps { public: - using Content = std::basic_string, - Allocator>; - String() {} String(const char* content) : m_data(content, (size_t)(int)strlen(content)) {} String(const char* content, ByteCount len) : m_data(content, (size_t)(int)len) {} @@ -109,30 +106,78 @@ public: String(const char* begin, const char* end) : m_data(begin, end-begin) {} [[gnu::always_inline]] - char* data() { return &m_data[0]; } + char* data() { return m_data.data(); } [[gnu::always_inline]] const char* data() const { return m_data.data(); } [[gnu::always_inline]] - ByteCount length() const { return m_data.length(); } + ByteCount length() const { return m_data.size(); } [[gnu::always_inline]] - const char* c_str() const { return m_data.c_str(); } + const char* c_str() const { return m_data.data(); } [[gnu::always_inline]] void append(const char* data, ByteCount count) { m_data.append(data, (size_t)(int)count); } void clear() { m_data.clear(); } - void push_back(char c) { m_data.push_back(c); } - void resize(ByteCount size) { m_data.resize((size_t)(int)size); } + void push_back(char c) { m_data.append(&c, 1); } + void force_size(ByteCount size) { m_data.force_size((size_t)(int)size); } void reserve(ByteCount size) { m_data.reserve((size_t)(int)size); } static const String ms_empty; + union Data + { + using Alloc = Allocator; + + struct Long + { + char* ptr; + size_t size; + size_t capacity; + } l; + + struct Short + { + static constexpr size_t capacity = sizeof(Long) - 2; + char string[capacity+1]; + unsigned char size; + } s; + + Data() { set_empty(); } + Data(const char* data, size_t size, size_t capacity); + Data(const char* data, size_t size) : Data(data, size, size) {} + Data(const Data& other) : Data{other.data(), other.size()} {} + + ~Data() { release(); } + Data(Data&& other) noexcept; + Data& operator=(const Data& other); + Data& operator=(Data&& other) noexcept; + + bool is_long() const { return (s.size & 1) == 0; } + size_t size() const { return is_long() ? l.size : (s.size >> 1); } + size_t capacity() const { return is_long() ? l.capacity : Short::capacity; } + + const char* data() const { return is_long() ? l.ptr : s.string; } + char* data() { return is_long() ? l.ptr : s.string; } + + template + void reserve(size_t new_capacity); + void force_size(size_t new_size); + void append(const char* str, size_t len); + void clear(); + + private: + void release(); + void set_empty() { s.size = 1; } + void set_size(size_t size); + void set_short(const char* data, size_t size); + }; + private: - Content m_data; + Data m_data; }; class StringView : public StringOps @@ -162,12 +207,12 @@ public: if (*end == '\0') unowned = begin; else - owned = String::Content(begin, end); + owned = String::Data(begin, end - begin); } - operator const char*() const { return unowned ? unowned : owned.c_str(); } + operator const char*() const { return unowned ? unowned : owned.data(); } private: - String::Content owned; + String::Data owned; const char* unowned = nullptr; };