Files
pl/include/string.hpp

276 lines
5.9 KiB
C++

#pragma once
#include <cstddef>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <utility>
template<typename T>
struct View
{
size_t size;
T* data; // owns its memory
public:
View() : size(0), data(nullptr) {}
// Deep copy constructor
View(const T* src, size_t count) : size(count)
{
if (count == 0) { data = nullptr; return; }
data = static_cast<T*>(::operator new[](count * sizeof(T)));
for (size_t i = 0; i < count; ++i)
new (&data[i]) T(src[i]); // copy-construct
}
// Copy constructor
View(const View& other) : View(other.data, other.size) {}
// Move constructor
View(View&& other) noexcept : size(other.size), data(other.data)
{
other.size = 0;
other.data = nullptr;
}
View &operator=(const View &other)
{
if (this != &other)
{
// free old memory
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
// deep copy
size = other.size;
data = static_cast<T *>(::operator new[](size * sizeof(T)));
for (size_t i = 0; i < size; ++i)
new (&data[i]) T(other.data[i]);
}
return *this;
}
// Destructor
~View()
{
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
}
const T* begin() const { return data; }
const T* end() const { return data + size; }
};
// using StringView = View<char>;
class StringView final : public View<char>
{
public:
StringView() : View<char>() {}
// Construct from C-string — deep copy, including null terminator
StringView(const char* s)
: View<char>(s, strlen(s) + 1) {}
// Construct from View<char> — ensure null termination
StringView(const View<char>& v)
: View<char>(v)
{
if (size == 0 || data[size - 1] != '\0')
{
// Reallocate and append null terminator
size_t newSize = size + 1;
char* newData = static_cast<char*>(::operator new[](newSize * sizeof(char)));
for (size_t i = 0; i < size; ++i)
newData[i] = data[i];
newData[newSize - 1] = '\0';
// destroy old
// for (size_t i = 0; i < size; ++i)
// data[i].~char();
::operator delete[](data);
data = newData;
size = newSize;
}
}
const char* c_str() const { return data; }
};
template<typename T>
struct Builder
{
size_t size = 0;
size_t capacity = 8;
T* data;
public:
Builder()
{
data = static_cast<T*>(::operator new[](capacity * sizeof(T)));
}
~Builder()
{
clear();
::operator delete[](data);
}
protected:
void grow(size_t newSize)
{
if (newSize <= capacity) return;
size_t newCap = capacity;
while (newCap < newSize)
newCap += newCap / 2;
T* newData = static_cast<T*>(::operator new[](newCap * sizeof(T)));
// Move-construct into the new buffer
for (size_t i = 0; i < size; ++i)
new (&newData[i]) T(std::move(data[i]));
// Destroy old items
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
data = newData;
capacity = newCap;
}
public:
void Push(const T& value)
{
grow(size + 1);
new (&data[size]) T(value);
size++;
}
void Push(T&& value)
{
grow(size + 1);
new (&data[size]) T(std::move(value));
size++;
}
// Clear Builder storage but keep capacity
void clear()
{
for (size_t i = 0; i < size; ++i)
data[i].~T();
size = 0;
}
// ALWAYS produce a deep-copied View
View<T> view() const
{
return View<T>(data, size);
}
};
// using StringBuilder = Builder<char>;
class StringBuilder final : public Builder<char>
{
public:
StringBuilder() : Builder<char>() {}
// Ensure there is room for `n` more characters (not counting terminator)
void ensure_extra(size_t n)
{
// grow size + n (but not including terminator)
this->grow(this->size + n);
}
// Append raw C string (WITHOUT copying terminator)
void Extend(const char* str)
{
if (!str) return;
size_t len = strlen(str);
ensure_extra(len);
for (size_t i = 0; i < len; ++i)
this->Push(str[i]);
}
// Append a single char
void Append(char c)
{
this->Push(c);
}
void AppendIndent(int indent) {
grow(size + indent);
memset(data + size, ' ', indent);
size += indent;
}
// Return a null-terminated string pointer owned by builder
const char* c_str()
{
// ensure space for terminator
this->grow(this->size + 1);
// add terminator (overwrite or place it at end)
if (this->size == 0 || this->data[this->size - 1] != '\0')
{
// If already ended with \0, fine
this->Push('\0');
}
return this->data;
}
// Produce a deep-copied StringView
StringView view()
{
return StringView(this->c_str());
}
// streaming operators
StringBuilder& operator<<(const char* s)
{
Extend(s);
return *this;
}
StringBuilder& operator<<(const StringView& sv)
{
Extend(sv.c_str());
return *this;
}
StringBuilder& operator<<(char c)
{
Append(c);
return *this;
}
StringBuilder& operator<<(int v)
{
char buf[32];
snprintf(buf, sizeof(buf), "%d", v);
Extend(buf);
return *this;
}
StringBuilder& operator<<(long v)
{
char buf[32];
snprintf(buf, sizeof(buf), "%ld", v);
Extend(buf);
return *this;
}
};