397 lines
8.8 KiB
C++
397 lines
8.8 KiB
C++
#pragma once
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <cstdarg>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <utility>
|
|
#include <functional>
|
|
|
|
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; }
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static StringView FromFormat(const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
|
|
int len = std::vsnprintf(nullptr, 0, fmt, args2);
|
|
va_end(args2);
|
|
|
|
if (len < 0) {
|
|
va_end(args);
|
|
return StringView();
|
|
}
|
|
|
|
char* str = new char[len + 1];
|
|
std::vsnprintf(str, len + 1, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
return StringView(str);
|
|
}
|
|
|
|
size_t len() const { return strlen(data); }
|
|
|
|
const char* c_str() const { return data; }
|
|
};
|
|
|
|
namespace std
|
|
{
|
|
template<>
|
|
struct hash<StringView>
|
|
{
|
|
size_t operator()(const StringView& sv) const noexcept
|
|
{
|
|
// FNV-1a hash (simple, fast, good enough)
|
|
constexpr size_t fnv_offset = 14695981039346656037ull;
|
|
constexpr size_t fnv_prime = 1099511628211ull;
|
|
|
|
size_t hash = fnv_offset;
|
|
|
|
// Exclude the null terminator if present
|
|
size_t len = (sv.size > 0 && sv.data[sv.size - 1] == '\0')
|
|
? sv.size - 1
|
|
: sv.size;
|
|
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
hash ^= static_cast<unsigned char>(sv.data[i]);
|
|
hash *= fnv_prime;
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
};
|
|
}
|
|
|
|
inline bool operator==(const StringView& a, const StringView& b)
|
|
{
|
|
// Sizes include null terminator; logical string length is size-1
|
|
if (a.size != b.size) return false;
|
|
if (a.size == 0) return true;
|
|
return std::memcmp(a.data, b.data, a.size) == 0;
|
|
}
|
|
|
|
inline bool operator!=(const StringView& a, const StringView& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void VAppendFormat(const char* fmt, va_list args)
|
|
{
|
|
// Make a copy because vsnprintf consumes the va_list
|
|
va_list argsCopy;
|
|
va_copy(argsCopy, args);
|
|
|
|
int len = std::vsnprintf(nullptr, 0, fmt, argsCopy);
|
|
va_end(argsCopy);
|
|
|
|
if (len <= 0)
|
|
return;
|
|
|
|
this->grow(this->size + len + 1);
|
|
|
|
std::vsnprintf(this->data + this->size, len + 1, fmt, args);
|
|
this->size += len;
|
|
}
|
|
|
|
|
|
void AppendFormat(const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
// 1) compute length (excluding terminator)
|
|
va_list argsCopy;
|
|
va_copy(argsCopy, args);
|
|
int len = std::vsnprintf(nullptr, 0, fmt, argsCopy);
|
|
va_end(argsCopy);
|
|
|
|
if (len <= 0)
|
|
{
|
|
va_end(args);
|
|
return;
|
|
}
|
|
|
|
// 2) reserve space
|
|
this->grow(this->size + len + 1);
|
|
|
|
// 3) write formatted string
|
|
std::vsnprintf(this->data + this->size, len + 1, fmt, args);
|
|
this->size += len;
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
StringBuilder& operator<<(unsigned int v)
|
|
{
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%d", v);
|
|
Extend(buf);
|
|
return *this;
|
|
}
|
|
};
|