diff --git a/libluna/CMakeLists.txt b/libluna/CMakeLists.txt index 7b49e9d1..5c2d3da6 100644 --- a/libluna/CMakeLists.txt +++ b/libluna/CMakeLists.txt @@ -19,6 +19,7 @@ set(FREESTANDING_SOURCES src/SHA.cpp src/Stack.cpp src/String.cpp + src/RefString.cpp src/StringBuilder.cpp src/StringView.cpp src/Utf8.cpp diff --git a/libluna/include/luna/RefString.h b/libluna/include/luna/RefString.h new file mode 100644 index 00000000..91e08aec --- /dev/null +++ b/libluna/include/luna/RefString.h @@ -0,0 +1,120 @@ +/** + * @file RefString.h + * @author apio (cloudapio.eu) + * @brief Reference-counted, trivially-copyable, immutable string type. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +class RefString +{ + typedef const char* ConstIterator; + + public: + /* Constructs an empty String. */ + RefString(); + + RefString(RefString&&); + + RefString(const RefString&); + + RefString& operator=(const RefString&); + + bool operator==(const RefString& other) const + { + return !compare(this, &other); + } + + ~RefString() = default; + + /* Creates a copy of a specific segment of this String and returns it. */ + Result substring(usize begin, usize size) const; + + /* Splits a String with a delimiter. */ + Result> split(StringView delim) const + { + return view().ref_split(delim); + } + + /* Splits a String into two parts with a delimiter. */ + Result> split_once(char delim) const + { + return view().ref_split_once(delim); + } + + /* Creates a single String consisting of a list of strings separated by a delimiter. */ + static Result join(const Vector& vec, StringView delim); + + /* Creates a single String consisting of a list of strings separated by a delimiter. */ + static Result join(const Vector& vec, StringView delim); + + static Result format(StringView fmt, ...); + static Result vformat(StringView fmt, va_list ap); + + static Result from_cstring(const char* str); + static Result from_string_view(StringView str); + static Result from_string(const String& str); + + static int compare(const RefString* a, const RefString* b); + + const char* chars() const; + + usize length() const + { + return m_string_data ? m_string_data->length : 0; + } + + bool is_empty() const + { + return length() == 0; + } + + StringView view() const + { + return { chars(), length() }; + } + + const char& operator[](usize) const; + + ConstIterator begin() const + { + return chars(); + } + + ConstIterator end() const + { + return begin() + length(); + } + + private: + struct RefStringData : public Shareable + { + char* string { nullptr }; + usize length { 0 }; + + RefStringData(char* s, usize l) : Shareable(), string(s), length(l) + { + } + + ~RefStringData() + { + if (string) free_impl(string); + } + }; + + SharedPtr m_string_data { nullptr }; + + static Result adopt(char* c_str); + static Result adopt(char* c_str, usize length); +}; + +template <> u64 hash(const RefString& value, u64 salt); diff --git a/libluna/include/luna/StringView.h b/libluna/include/luna/StringView.h index 7f12b0db..1e2b29de 100644 --- a/libluna/include/luna/StringView.h +++ b/libluna/include/luna/StringView.h @@ -4,6 +4,7 @@ #include class String; +class RefString; class StringView { @@ -43,6 +44,8 @@ class StringView Result> split(StringView delim) const; Result> split_once(char delim) const; + Result> ref_split(StringView delim) const; + Result> ref_split_once(char delim) const; Result> split_view(char delim) const; bool contains(char v) const; diff --git a/libluna/src/RefString.cpp b/libluna/src/RefString.cpp new file mode 100644 index 00000000..3f1b4e0a --- /dev/null +++ b/libluna/src/RefString.cpp @@ -0,0 +1,122 @@ +/** + * @file RefString.cpp + * @author apio (cloudapio.eu) + * @brief Reference-counted, trivially-copyable, immutable string type. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#include +#include + +static const char empty_string[] = { 0 }; + +RefString::RefString() +{ +} + +RefString::RefString(RefString&& other) : m_string_data(move(other.m_string_data)) +{ +} + +RefString::RefString(const RefString& other) : m_string_data(other.m_string_data) +{ +} + +const char* RefString::chars() const +{ + if (m_string_data) return m_string_data->string; + return empty_string; +} + +RefString& RefString::operator=(const RefString& other) +{ + if (&other == this) return *this; + m_string_data = other.m_string_data; + return *this; +} + +Result RefString::substring(usize begin, usize size) const +{ + if (begin + size >= size) return err(EINVAL); + char* const dup = strndup(chars() + begin, size); + if (!dup) return err(ENOMEM); + return adopt(dup, size); +} + +const char& RefString::operator[](usize index) const +{ + expect(index < length(), "index out of range"); + return chars()[index]; +} + +Result RefString::format(StringView fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + auto rc = vformat(fmt, ap); + + va_end(ap); + + return rc; +} + +Result RefString::vformat(StringView fmt, va_list ap) +{ + Vector buf; + + TRY(cstyle_format( + fmt.chars(), [](char c, void* data) -> Result { return ((Vector*)data)->try_append(c); }, &buf, + ap)); + + TRY(buf.try_append(0)); + + return adopt(buf.release_data()); +} + +Result RefString::from_cstring(const char* str) +{ + return from_string_view(StringView { str }); +} + +Result RefString::from_string(const String& str) +{ + return from_string_view(str.view()); +} + +Result RefString::from_string_view(StringView str) +{ + char* const dup = strndup(str.chars(), str.length()); + if (!dup) return err(ENOMEM); + return adopt(dup, str.length()); +} + +int RefString::compare(const RefString* a, const RefString* b) +{ + return strcmp(a->chars(), b->chars()); +} + +Result RefString::adopt(char* c_str) +{ + return adopt(c_str, strlen(c_str)); +} + +Result RefString::adopt(char* c_str, usize length) +{ + auto guard = make_scope_guard([&] { free_impl(c_str); }); + + auto data = TRY(make_shared(c_str, length)); + + guard.deactivate(); + + RefString string; + string.m_string_data = data; + return string; +} + +template <> u64 hash(const RefString& value, u64 salt) +{ + return hash_memory(value.chars(), value.length(), salt); +} diff --git a/libluna/src/StringView.cpp b/libluna/src/StringView.cpp index 00dbdb32..51f1d338 100644 --- a/libluna/src/StringView.cpp +++ b/libluna/src/StringView.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -86,6 +87,32 @@ Result> StringView::split(StringView delim) const return result; } +Result> StringView::ref_split(StringView delim) const +{ + Vector result; + RefString str; + + char* copy = strndup(m_string, m_length); + auto guard = make_scope_guard([copy] { free_impl(copy); }); + + char* segment = strtok(copy, delim.chars()); + if (!segment) return result; + + str = TRY(RefString::from_cstring(segment)); + TRY(result.try_append(move(str))); + + while (true) + { + segment = strtok(nullptr, delim.chars()); + if (!segment) return result; + + str = TRY(RefString::from_cstring(segment)); + TRY(result.try_append(move(str))); + } + + return result; +} + Result> StringView::split_once(char delim) const { Vector result; @@ -112,6 +139,32 @@ Result> StringView::split_once(char delim) const return result; } +Result> StringView::ref_split_once(char delim) const +{ + Vector result; + + char* copy = strndup(m_string, m_length); + auto guard = make_scope_guard([copy] { free_impl(copy); }); + + char* middle = strchr(copy, delim); + if (!middle) + { + auto str = TRY(RefString::from_cstring(copy)); + TRY(result.try_append(move(str))); + return result; + } + + *middle = '\0'; + + auto begin = TRY(RefString::from_cstring(copy)); + auto end = TRY(RefString::from_cstring(middle + 1)); + + TRY(result.try_append(move(begin))); + TRY(result.try_append(move(end))); + + return result; +} + Result> StringView::split_view(char delim) const { Vector result;