diff options
author | Steven Le Rouzic <steven.lerouzic@gmail.com> | 2025-02-17 00:21:48 +0100 |
---|---|---|
committer | Steven Le Rouzic <steven.lerouzic@gmail.com> | 2025-02-17 22:29:50 +0100 |
commit | a141c401f78467bc15f62882fca5d55a007cacbb (patch) | |
tree | 908ac71a8640f78f45d22c6808c5fa6e373000fa /asl/strings | |
parent | cb77cbe9ce4cddad6a460aa190ff70f0c13e4703 (diff) |
Reorganize everything
Diffstat (limited to 'asl/strings')
-rw-r--r-- | asl/strings/BUILD.bazel | 56 | ||||
-rw-r--r-- | asl/strings/string.hpp | 77 | ||||
-rw-r--r-- | asl/strings/string_builder.hpp | 156 | ||||
-rw-r--r-- | asl/strings/string_builder_tests.cpp | 23 | ||||
-rw-r--r-- | asl/strings/string_tests.cpp | 21 | ||||
-rw-r--r-- | asl/strings/string_view.hpp | 107 | ||||
-rw-r--r-- | asl/strings/string_view_tests.cpp | 117 |
7 files changed, 557 insertions, 0 deletions
diff --git a/asl/strings/BUILD.bazel b/asl/strings/BUILD.bazel new file mode 100644 index 0000000..b9032dc --- /dev/null +++ b/asl/strings/BUILD.bazel @@ -0,0 +1,56 @@ +cc_library( + name = "string_view", + hdrs = [ + "string_view.hpp", + ], + deps = [ + "//asl/base", + "//asl/memory", + "//asl/types:span", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "string", + hdrs = [ + "string.hpp", + ], + deps = [ + "//asl/containers:buffer", + ":string_view", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "string_builder", + hdrs = [ + "string_builder.hpp", + ], + deps = [ + "//asl/containers:buffer", + "//asl/formatting", + ":string", + "//asl/io:writer", + ], + visibility = ["//visibility:public"], +) + +[cc_test( + name = "%s_tests" % name, + srcs = [ + "%s_tests.cpp" % name, + ], + deps = [ + ":string", + ":string_builder", + ":string_view", + "//asl/tests:utils", + "//asl/testing", + ], +) for name in [ + "string", + "string_view", + "string_builder", +]] diff --git a/asl/strings/string.hpp b/asl/strings/string.hpp new file mode 100644 index 0000000..abad3b4 --- /dev/null +++ b/asl/strings/string.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "asl/containers/buffer.hpp" +#include "asl/strings/string_view.hpp" + +namespace asl +{ + +template<allocator Allocator = DefaultAllocator> +class string +{ + buffer<char, Allocator> m_buffer; + + explicit constexpr string(buffer<char, Allocator>&& buffer) : + m_buffer{ASL_MOVE(buffer)} + {} + + template<allocator A> + friend class StringBuilder; + +public: + constexpr string() requires default_constructible<Allocator> = default; + explicit constexpr string(Allocator allocator) : m_buffer{ASL_MOVE(allocator)} {} + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr string(string_view sv) + requires default_constructible<Allocator> + : m_buffer{sv.as_span()} + {} + + constexpr string(string_view sv, Allocator allocator) + : m_buffer{sv.as_span(), ASL_MOVE(allocator)} + {} + + constexpr ~string() = default; + + constexpr string(const string&) requires copy_constructible<Allocator> = default; + constexpr string(string&&) = default; + + constexpr string& operator=(const string&) requires copy_assignable<Allocator> = default; + constexpr string& operator=(string&&) = default; + + constexpr isize_t size() const { return m_buffer.size(); } + constexpr const char* data() const { return m_buffer.data(); } + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr operator string_view() const + { + return as_string_view(); + } + + constexpr string_view as_string_view() const + { + auto span = m_buffer.as_span(); + return string_view{span.data(), span.size()}; + } + + constexpr bool operator==(const string& other) const + { + return as_string_view() == other.as_string_view(); + } + + constexpr bool operator==(string_view other) const + { + return as_string_view() == other; + } + + template<typename H> + friend H AslHashValue(H h, const string& str) + { + return H::combine(h, str.as_string_view()); + } +}; + +string() -> string<>; + +} // namespace asl diff --git a/asl/strings/string_builder.hpp b/asl/strings/string_builder.hpp new file mode 100644 index 0000000..d30f1d5 --- /dev/null +++ b/asl/strings/string_builder.hpp @@ -0,0 +1,156 @@ +#pragma once + +#include "asl/containers/buffer.hpp" +#include "asl/strings/string.hpp" +#include "asl/strings/string_view.hpp" +#include "asl/formatting/format.hpp" +#include "asl/io/writer.hpp" + +namespace asl +{ + +template<allocator Allocator = DefaultAllocator> +class StringBuilder +{ + buffer<char, Allocator> m_buffer; + +public: + constexpr StringBuilder() requires default_constructible<Allocator> = default; + explicit constexpr StringBuilder(Allocator allocator) : m_buffer{ASL_MOVE(allocator)} {} + + constexpr ~StringBuilder() = default; + + constexpr StringBuilder(const StringBuilder&) requires copy_constructible<Allocator> = default; + constexpr StringBuilder(StringBuilder&&) = default; + + constexpr StringBuilder& operator=(const StringBuilder&) requires copy_assignable<Allocator> = default; + constexpr StringBuilder& operator=(StringBuilder&&) = default; + + constexpr string_view as_string_view() const + { + auto span = m_buffer.as_span(); + return string_view{span.data(), span.size()}; + } + + void reset() + { + m_buffer.clear(); + } + + // @Todo(C++23) Deducing this + + StringBuilder& push(string_view sv) & + { + isize_t old_size = m_buffer.size(); + m_buffer.resize_zero(old_size + sv.size()); + asl::memcpy(m_buffer.data() + old_size, sv.data(), sv.size()); + return *this; + } + + StringBuilder&& push(string_view sv) && + { + isize_t old_size = m_buffer.size(); + m_buffer.resize_zero(old_size + sv.size()); + asl::memcpy(m_buffer.data() + old_size, sv.data(), sv.size()); + return ASL_MOVE(*this); + } + + StringBuilder& push(char c) & + { + m_buffer.push(c); + return *this; + } + + StringBuilder&& push(char c) && + { + m_buffer.push(c); + return ASL_MOVE(*this); + } + + string<Allocator> finish() && + { + return string<Allocator>{ASL_MOVE(m_buffer)}; + } + + template<allocator StringAllocator = Allocator> + string<StringAllocator> as_string() + requires default_constructible<StringAllocator> + { + return string<StringAllocator>{as_string_view()}; + } + + template<allocator StringAllocator = Allocator> + string<StringAllocator> as_string(Allocator allocator) + { + return string<StringAllocator>{as_string_view(), ASL_MOVE(allocator)}; + } +}; + +StringBuilder() -> StringBuilder<>; + +template<typename Allocator = DefaultAllocator> +class StringWriter : public asl::Writer +{ + StringBuilder<Allocator> m_builder; + +public: + constexpr StringWriter() requires default_constructible<Allocator> = default; + explicit constexpr StringWriter(Allocator allocator) : m_builder{ASL_MOVE(allocator)} {} + + constexpr ~StringWriter() override = default; + + constexpr StringWriter(const StringWriter&) requires copy_constructible<Allocator> = default; + constexpr StringWriter(StringWriter&&) = default; + + constexpr StringWriter& operator=(const StringWriter&) requires copy_assignable<Allocator> = default; + constexpr StringWriter& operator=(StringWriter&&) = default; + + void write(span<const byte> str) override + { + m_builder.push(string_view{reinterpret_cast<const char*>(str.data()), str.size()}); + } + + constexpr string_view as_string_view() const + { + return m_builder.as_string_view(); + } + + string<Allocator> finish() && + { + return ASL_MOVE(m_builder).finish(); + } + + template<allocator StringAllocator = Allocator> + string<StringAllocator> as_string() + requires default_constructible<StringAllocator> + { + return m_builder.as_string(); + } + + template<allocator StringAllocator = Allocator> + string<StringAllocator> as_string(Allocator allocator) + { + return m_builder.as_string(ASL_MOVE(allocator)); + } +}; + +StringWriter() -> StringWriter<>; + +template<allocator Allocator = DefaultAllocator, formattable... Args> +string<Allocator> format_to_string(string_view fmt, const Args&... args) + requires default_constructible<Allocator> +{ + StringWriter writer{}; + format(&writer, fmt, args...); + return ASL_MOVE(writer).finish(); +} + +template<allocator Allocator = DefaultAllocator, formattable... Args> +string<Allocator> format_to_string(Allocator allocator, string_view fmt, const Args&... args) +{ + StringWriter writer{ASL_MOVE(allocator)}; + format(&writer, fmt, args...); + return ASL_MOVE(writer).finish(); +} + +} // namespace asl diff --git a/asl/strings/string_builder_tests.cpp b/asl/strings/string_builder_tests.cpp new file mode 100644 index 0000000..5341af2 --- /dev/null +++ b/asl/strings/string_builder_tests.cpp @@ -0,0 +1,23 @@ +#include "asl/strings/string_builder.hpp" +#include "asl/testing/testing.hpp" + +ASL_TEST(string_builder) +{ + asl::StringBuilder b; + b.push('a'); + b.push("bcdef"); + b.push('g'); + + ASL_TEST_EXPECT(b.as_string_view() == "abcdefg"); + + asl::string s = b.as_string(); + ASL_TEST_EXPECT(s == "abcdefg"); +} + +ASL_TEST(string_builder_rvalue) +{ + asl::string s = asl::StringBuilder{}.push('a').push("bcdef").push('g').finish(); + + ASL_TEST_EXPECT(s == "abcdefg"); +} + diff --git a/asl/strings/string_tests.cpp b/asl/strings/string_tests.cpp new file mode 100644 index 0000000..dda8f74 --- /dev/null +++ b/asl/strings/string_tests.cpp @@ -0,0 +1,21 @@ +#include "asl/strings/string.hpp" +#include "asl/testing/testing.hpp" +#include "asl/formatting/format.hpp" + +ASL_TEST(default) +{ + asl::string s; + ASL_TEST_ASSERT(s.size() == 0); + ASL_TEST_ASSERT(s.as_string_view().size() == 0); + ASL_TEST_ASSERT(s == ""_sv); + ASL_TEST_ASSERT(s == s); +} + +ASL_TEST(from_string_view) +{ + asl::string s = "hello"_sv; + ASL_TEST_ASSERT(s.size() == 5); + ASL_TEST_ASSERT(s == "hello"_sv); +} + +static_assert(asl::formattable<asl::string<>>); diff --git a/asl/strings/string_view.hpp b/asl/strings/string_view.hpp new file mode 100644 index 0000000..9d8b576 --- /dev/null +++ b/asl/strings/string_view.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "asl/base/integers.hpp" +#include "asl/base/meta.hpp" +#include "asl/types/span.hpp" +#include "asl/memory/memory.hpp" + +namespace asl +{ + +class string_view +{ + const char* m_data{}; + isize_t m_size{}; + +public: + constexpr string_view() = default; + + constexpr string_view(nullptr_t) : string_view() {} // NOLINT(*-explicit-conversions) + + constexpr string_view(const char* data, isize_t size) + : m_data{data} + , m_size{size} + {} + + template<isize_t kSize> + constexpr string_view(const char (&str)[kSize]) // NOLINT(*-explicit-conversions) + requires (kSize >= 1) + : m_data{str} + , m_size{kSize - 1} + { + ASL_ASSERT(m_data[kSize - 1] == '\0'); // NOLINT(*-pointer-arithmetic) + } + + static constexpr string_view from_zstr(const char* str) + { + return string_view(str, asl::strlen(str)); + } + + constexpr string_view(const string_view&) = default; + constexpr string_view(string_view&&) = default; + + constexpr string_view& operator=(const string_view&) = default; + constexpr string_view& operator=(string_view&&) = default; + + ~string_view() = default; + + constexpr isize_t size() const { return m_size; } + + constexpr bool is_empty() const { return m_size == 0; } + + constexpr const char* data() const { return m_data; } + + constexpr contiguous_iterator<const char> begin() const { return contiguous_iterator{m_data}; } + + // NOLINTNEXTLINE(*-pointer-arithmetic) + constexpr contiguous_iterator<const char> end() const { return contiguous_iterator{m_data + m_size}; } + + constexpr span<const char> as_span() const { return span<const char>(m_data, m_size); } + + constexpr char operator[](isize_t i) const + { + ASL_ASSERT(i >= 0 && i < m_size); + return m_data[i]; // NOLINT(*-pointer-arithmetic) + } + + constexpr string_view substr(isize_t offset, isize_t size) const + { + ASL_ASSERT(offset >= 0 && size >= 0 && offset + size <= m_size); + return string_view{m_data + offset, size}; // NOLINT(*-pointer-arithmetic) + } + + constexpr string_view substr(isize_t offset) const + { + ASL_ASSERT(offset >= 0 && offset <= m_size); + return string_view{m_data + offset, m_size - offset}; // NOLINT(*-pointer-arithmetic) + } + + constexpr string_view first(isize_t size) const + { + return substr(0, size); + } + + constexpr string_view last(isize_t size) const + { + return substr(m_size - size); + } + + constexpr bool operator==(string_view other) const + { + if (m_size != other.m_size) { return false; } + return memcmp(m_data, other.m_data, m_size) == 0; + } + + template<typename H> + friend H AslHashValue(H h, string_view sv) + { + return H::combine(H::combine_contiguous(h, as_bytes(sv.as_span())), sv.size()); + } +}; + +} // namespace asl + +constexpr asl::string_view operator ""_sv(const char* s, size_t len) +{ + return asl::string_view(s, static_cast<isize_t>(len)); +} diff --git a/asl/strings/string_view_tests.cpp b/asl/strings/string_view_tests.cpp new file mode 100644 index 0000000..a357186 --- /dev/null +++ b/asl/strings/string_view_tests.cpp @@ -0,0 +1,117 @@ +#include "asl/strings/string_view.hpp" +#include "asl/testing/testing.hpp" + +static_assert(asl::trivially_destructible<asl::string_view>); +static_assert(asl::trivially_copy_constructible<asl::string_view>); + +ASL_TEST(default) +{ + asl::string_view s1; + ASL_TEST_EXPECT(s1.is_empty()); + + asl::string_view s2 = nullptr; + ASL_TEST_EXPECT(s2.is_empty()); +} + +ASL_TEST(from_literal) +{ + asl::string_view s1 = "Hello"_sv; + ASL_TEST_ASSERT(s1.size() == 5); + ASL_TEST_EXPECT(asl::memcmp(s1.data(), "Hello", 5) == 0); + + asl::string_view s2 = ""_sv; + ASL_TEST_EXPECT(s2.is_empty()); +} + +ASL_TEST(from_zstr) +{ + const char* s1 = ""; + const char* s2 = "abc"; + const char* s3 = "abc\0def"; + + auto sv1 = asl::string_view::from_zstr(s1); + auto sv2 = asl::string_view::from_zstr(s2); + auto sv3 = asl::string_view::from_zstr(s3); + + ASL_TEST_ASSERT(sv1.size() == 0); + ASL_TEST_ASSERT(sv2.size() == 3); + ASL_TEST_ASSERT(sv3.size() == 3); + + ASL_TEST_ASSERT(sv2 == "abc"_sv); + ASL_TEST_ASSERT(sv3 == "abc"_sv); +} + +ASL_TEST(substr1) +{ + asl::string_view s1 = "abcd"; + + asl::string_view s2 = s1.substr(0); + ASL_TEST_ASSERT(s2.size() == 4); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "abcd", 4) == 0); + + s2 = s1.substr(2); + ASL_TEST_ASSERT(s2.size() == 2); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "cd", 2) == 0); + + s2 = s1.substr(4); + ASL_TEST_ASSERT(s2.size() == 0); +} + +ASL_TEST(substr2) +{ + asl::string_view s1 = "abcd"; + + asl::string_view s2 = s1.substr(0, 4); + ASL_TEST_ASSERT(s2.size() == 4); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "abcd", 4) == 0); + + s2 = s1.substr(1, 2); + ASL_TEST_ASSERT(s2.size() == 2); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "bc", 2) == 0); + + s2 = s1.substr(4, 0); + ASL_TEST_ASSERT(s2.size() == 0); + + s2 = s1.substr(1, 0); + ASL_TEST_ASSERT(s2.size() == 0); +} + +ASL_TEST(first) +{ + asl::string_view s1 = "abcd"; + + asl::string_view s2 = s1.first(0); + ASL_TEST_ASSERT(s2.size() == 0); + + s2 = s1.first(2); + ASL_TEST_ASSERT(s2.size() == 2); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "ab", 2) == 0); + + s2 = s1.first(4); + ASL_TEST_ASSERT(s2.size() == 4); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "abcd", 4) == 0); +} + +ASL_TEST(last) +{ + asl::string_view s1 = "abcd"; + + asl::string_view s2 = s1.last(0); + ASL_TEST_ASSERT(s2.size() == 0); + + s2 = s1.last(2); + ASL_TEST_ASSERT(s2.size() == 2); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "cd", 2) == 0); + + s2 = s1.last(4); + ASL_TEST_ASSERT(s2.size() == 4); + ASL_TEST_EXPECT(asl::memcmp(s2.data(), "abcd", 4) == 0); +} + +ASL_TEST(equal) +{ + ASL_TEST_EXPECT("abc"_sv == "abc"_sv); + ASL_TEST_EXPECT(""_sv == ""_sv); + ASL_TEST_EXPECT("abc"_sv != "ab"_sv); + ASL_TEST_EXPECT("abc"_sv != "abd"_sv); +} |