summaryrefslogtreecommitdiff
path: root/asl/strings
diff options
context:
space:
mode:
Diffstat (limited to 'asl/strings')
-rw-r--r--asl/strings/BUILD.bazel56
-rw-r--r--asl/strings/string.hpp77
-rw-r--r--asl/strings/string_builder.hpp156
-rw-r--r--asl/strings/string_builder_tests.cpp23
-rw-r--r--asl/strings/string_tests.cpp21
-rw-r--r--asl/strings/string_view.hpp107
-rw-r--r--asl/strings/string_view_tests.cpp117
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);
+}