diff options
-rw-r--r-- | source/blender/blenlib/BLI_string_ref.h | 244 | ||||
-rw-r--r-- | source/blender/blenlib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/gtests/blenlib/BLI_string_ref_test.cc | 230 | ||||
-rw-r--r-- | tests/gtests/blenlib/CMakeLists.txt | 1 |
4 files changed, 476 insertions, 0 deletions
diff --git a/source/blender/blenlib/BLI_string_ref.h b/source/blender/blenlib/BLI_string_ref.h new file mode 100644 index 00000000000..9d4678f335f --- /dev/null +++ b/source/blender/blenlib/BLI_string_ref.h @@ -0,0 +1,244 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bli + * + * A StringRef is a pointer to a string somewhere in memory. It should not be used to transfer + * ownership of that string. When a function gets a StringRef as input, it cannot expect, that + * the string will still exist after the function ends. + * + * There are two types of string references: One that guarantees null termination and one that does + * not. + */ + +#pragma once + +#include <cstring> +#include <string> +#include <sstream> + +#include "BLI_utildefines.h" +#include "BLI_array_ref.h" + +namespace BLI { + +class StringRef; + +class StringRefBase { + public: + using size_type = size_t; + + protected: + const char *m_data; + size_type m_size; + + StringRefBase(const char *data, size_type size) : m_data(data), m_size(size) + { + } + + public: + /** + * Return the (byte-)length of the referenced string, without any null-terminator. + */ + size_type size() const + { + return m_size; + } + + /** + * Return a pointer to the start of the string. + */ + const char *data() const + { + return m_data; + } + + char operator[](size_type index) const + { + BLI_assert(index <= m_size); + return m_data[index]; + } + + operator ArrayRef<char>() const + { + return ArrayRef<char>(m_data, m_size); + } + + operator std::string() const + { + return std::string(m_data, m_size); + } + + const char *begin() const + { + return m_data; + } + + const char *end() const + { + return m_data + m_size; + } + + void copy_to__with_null(char *dst) const + { + memcpy(dst, m_data, m_size); + dst[m_size] = '\0'; + } + + /** + * Returns true when the string begins with the given prefix. Otherwise false. + */ + bool startswith(StringRef prefix) const; + + /** + * Returns true when the string ends with the given suffix. Otherwise false. + */ + bool endswith(StringRef suffix) const; +}; + +/** + * References a null-terminated char array. + */ +class StringRefNull : public StringRefBase { + + public: + StringRefNull() : StringRefBase("", 0) + { + } + + StringRefNull(const char *str) : StringRefBase(str, strlen(str)) + { + BLI_assert(str != NULL); + BLI_assert(m_data[m_size] == '\0'); + } + + StringRefNull(const char *str, size_type size) : StringRefBase(str, size) + { + BLI_assert(str[size] == '\0'); + } + + StringRefNull(const std::string &str) : StringRefNull(str.data()) + { + } +}; + +/** + * References a char array. It might not be null terminated. + */ +class StringRef : public StringRefBase { + public: + StringRef() : StringRefBase(nullptr, 0) + { + } + + StringRef(StringRefNull other) : StringRefBase(other.data(), other.size()) + { + } + + StringRef(const char *str) : StringRefBase(str, str ? strlen(str) : 0) + { + } + + StringRef(const char *str, size_type length) : StringRefBase(str, length) + { + } + + StringRef(const std::string &str) : StringRefBase(str.data(), str.size()) + { + } + + /** + * Return a new StringRef that does not contain the first n chars. + */ + StringRef drop_prefix(uint n) const + { + BLI_assert(n <= m_size); + return StringRef(m_data + n, m_size - n); + } + + /** + * Return a new StringRef that with the given prefix being skipped. + * Asserts that the string begins with the given prefix. + */ + StringRef drop_prefix(StringRef prefix) const + { + BLI_assert(this->startswith(prefix)); + return this->drop_prefix(prefix.size()); + } +}; + +/* More inline functions + ***************************************/ + +inline std::ostream &operator<<(std::ostream &stream, StringRef ref) +{ + stream << std::string(ref); + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, StringRefNull ref) +{ + stream << std::string(ref.data(), ref.size()); + return stream; +} + +inline std::string operator+(StringRef a, StringRef b) +{ + return std::string(a) + std::string(b); +} + +inline bool operator==(StringRef a, StringRef b) +{ + if (a.size() != b.size()) { + return false; + } + return STREQLEN(a.data(), b.data(), a.size()); +} + +inline bool operator!=(StringRef a, StringRef b) +{ + return !(a == b); +} + +inline bool StringRefBase::startswith(StringRef prefix) const +{ + if (m_size < prefix.m_size) { + return false; + } + for (uint i = 0; i < prefix.m_size; i++) { + if (m_data[i] != prefix.m_data[i]) { + return false; + } + } + return true; +} + +inline bool StringRefBase::endswith(StringRef suffix) const +{ + if (m_size < suffix.m_size) { + return false; + } + uint offset = m_size - suffix.m_size; + for (uint i = 0; i < suffix.m_size; i++) { + if (m_data[offset + i] != suffix.m_data[i]) { + return false; + } + } + return true; +} + +} // namespace BLI diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index b4db305ccd4..abef583913c 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -225,6 +225,7 @@ set(SRC BLI_strict_flags.h BLI_string.h BLI_string_cursor_utf8.h + BLI_string_ref.h BLI_string_utf8.h BLI_string_utils.h BLI_sys_types.h diff --git a/tests/gtests/blenlib/BLI_string_ref_test.cc b/tests/gtests/blenlib/BLI_string_ref_test.cc new file mode 100644 index 00000000000..5605e10ac86 --- /dev/null +++ b/tests/gtests/blenlib/BLI_string_ref_test.cc @@ -0,0 +1,230 @@ +#include "testing/testing.h" +#include "BLI_string_ref.h" +#include "BLI_vector.h" + +using BLI::StringRef; +using BLI::StringRefNull; +using BLI::Vector; + +TEST(string_ref_null, DefaultConstructor) +{ + StringRefNull ref; + EXPECT_EQ(ref.size(), 0); + EXPECT_EQ(ref[0], '\0'); +} + +TEST(string_ref_null, CStringConstructor) +{ + const char *str = "Hello"; + StringRefNull ref(str); + EXPECT_EQ(ref.size(), 5); + EXPECT_EQ(ref.data(), str); +} + +TEST(string_ref_null, CStringLengthConstructor) +{ + const char *str = "Hello"; + StringRefNull ref(str, 5); + EXPECT_EQ(ref.size(), 5); + EXPECT_EQ(ref.data(), str); +} + +TEST(string_ref, DefaultConstructor) +{ + StringRef ref; + EXPECT_EQ(ref.size(), 0); +} + +TEST(string_ref, CStringConstructor) +{ + const char *str = "Test"; + StringRef ref(str); + EXPECT_EQ(ref.size(), 4); + EXPECT_EQ(ref.data(), str); +} + +TEST(string_ref, PointerWithLengthConstructor) +{ + const char *str = "Test"; + StringRef ref(str, 2); + EXPECT_EQ(ref.size(), 2); + EXPECT_EQ(ref.data(), str); +} + +TEST(string_ref, StdStringConstructor) +{ + std::string str = "Test"; + StringRef ref(str); + EXPECT_EQ(ref.size(), 4); + EXPECT_EQ(ref.data(), str.data()); +} + +TEST(string_ref, SubscriptOperator) +{ + StringRef ref("hello"); + EXPECT_EQ(ref.size(), 5); + EXPECT_EQ(ref[0], 'h'); + EXPECT_EQ(ref[1], 'e'); + EXPECT_EQ(ref[2], 'l'); + EXPECT_EQ(ref[3], 'l'); + EXPECT_EQ(ref[4], 'o'); +} + +TEST(string_ref, ToStdString) +{ + StringRef ref("test"); + std::string str = ref; + EXPECT_EQ(str.size(), 4); + EXPECT_EQ(str, "test"); +} + +TEST(string_ref, Print) +{ + StringRef ref("test"); + std::stringstream ss; + ss << ref; + ss << ref; + std::string str = ss.str(); + EXPECT_EQ(str.size(), 8); + EXPECT_EQ(str, "testtest"); +} + +TEST(string_ref, Add) +{ + StringRef a("qwe"); + StringRef b("asd"); + std::string result = a + b; + EXPECT_EQ(result, "qweasd"); +} + +TEST(string_ref, AddCharPtr1) +{ + StringRef ref("test"); + std::string result = ref + "qwe"; + EXPECT_EQ(result, "testqwe"); +} + +TEST(string_ref, AddCharPtr2) +{ + StringRef ref("test"); + std::string result = "qwe" + ref; + EXPECT_EQ(result, "qwetest"); +} + +TEST(string_ref, AddString1) +{ + StringRef ref("test"); + std::string result = ref + std::string("asd"); + EXPECT_EQ(result, "testasd"); +} + +TEST(string_ref, AddString2) +{ + StringRef ref("test"); + std::string result = std::string("asd") + ref; + EXPECT_EQ(result, "asdtest"); +} + +TEST(string_ref, CompareEqual) +{ + StringRef ref1("test"); + StringRef ref2("test"); + StringRef ref3("other"); + EXPECT_TRUE(ref1 == ref2); + EXPECT_FALSE(ref1 == ref3); + EXPECT_TRUE(ref1 != ref3); + EXPECT_FALSE(ref1 != ref2); +} + +TEST(string_ref, CompareEqualCharPtr1) +{ + StringRef ref("test"); + EXPECT_TRUE(ref == "test"); + EXPECT_FALSE(ref == "other"); + EXPECT_TRUE(ref != "other"); + EXPECT_FALSE(ref != "test"); +} + +TEST(string_ref, CompareEqualCharPtr2) +{ + StringRef ref("test"); + EXPECT_TRUE("test" == ref); + EXPECT_FALSE("other" == ref); + EXPECT_TRUE(ref != "other"); + EXPECT_FALSE(ref != "test"); +} + +TEST(string_ref, CompareEqualString1) +{ + StringRef ref("test"); + EXPECT_TRUE(ref == std::string("test")); + EXPECT_FALSE(ref == std::string("other")); + EXPECT_TRUE(ref != std::string("other")); + EXPECT_FALSE(ref != std::string("test")); +} + +TEST(string_ref, CompareEqualString2) +{ + StringRef ref("test"); + EXPECT_TRUE(std::string("test") == ref); + EXPECT_FALSE(std::string("other") == ref); + EXPECT_TRUE(std::string("other") != ref); + EXPECT_FALSE(std::string("test") != ref); +} + +TEST(string_ref, Iterate) +{ + StringRef ref("test"); + Vector<char> chars; + for (char c : ref) { + chars.append(c); + } + EXPECT_EQ(chars.size(), 4); + EXPECT_EQ(chars[0], 't'); + EXPECT_EQ(chars[1], 'e'); + EXPECT_EQ(chars[2], 's'); + EXPECT_EQ(chars[3], 't'); +} + +TEST(string_ref, StartsWith) +{ + StringRef ref("test"); + EXPECT_TRUE(ref.startswith("")); + EXPECT_TRUE(ref.startswith("t")); + EXPECT_TRUE(ref.startswith("te")); + EXPECT_TRUE(ref.startswith("tes")); + EXPECT_TRUE(ref.startswith("test")); + EXPECT_FALSE(ref.startswith("test ")); + EXPECT_FALSE(ref.startswith("a")); +} + +TEST(string_ref, EndsWith) +{ + StringRef ref("test"); + EXPECT_TRUE(ref.endswith("")); + EXPECT_TRUE(ref.endswith("t")); + EXPECT_TRUE(ref.endswith("st")); + EXPECT_TRUE(ref.endswith("est")); + EXPECT_TRUE(ref.endswith("test")); + EXPECT_FALSE(ref.endswith(" test")); + EXPECT_FALSE(ref.endswith("a")); +} + +TEST(string_ref, DropPrefixN) +{ + StringRef ref("test"); + StringRef ref2 = ref.drop_prefix(2); + StringRef ref3 = ref2.drop_prefix(2); + EXPECT_EQ(ref2.size(), 2); + EXPECT_EQ(ref3.size(), 0); + EXPECT_EQ(ref2, "st"); + EXPECT_EQ(ref3, ""); +} + +TEST(string_ref, DropPrefix) +{ + StringRef ref("test"); + StringRef ref2 = ref.drop_prefix("tes"); + EXPECT_EQ(ref2.size(), 1); + EXPECT_EQ(ref2, "t"); +} diff --git a/tests/gtests/blenlib/CMakeLists.txt b/tests/gtests/blenlib/CMakeLists.txt index d2d7b76df6c..d4281aa77b4 100644 --- a/tests/gtests/blenlib/CMakeLists.txt +++ b/tests/gtests/blenlib/CMakeLists.txt @@ -62,6 +62,7 @@ BLENDER_TEST(BLI_polyfill_2d "bf_blenlib") BLENDER_TEST(BLI_stack "bf_blenlib") BLENDER_TEST(BLI_stack_cxx "bf_blenlib") BLENDER_TEST(BLI_string "bf_blenlib") +BLENDER_TEST(BLI_string_ref "bf_blenlib") BLENDER_TEST(BLI_string_utf8 "bf_blenlib") BLENDER_TEST(BLI_task "bf_blenlib;bf_intern_numaapi") BLENDER_TEST(BLI_vector "bf_blenlib") |