forked from bartvdbraak/blender
BLI: new StringRef and StringRefNull data structures
These two data structures reference strings somewhere in memory. They do not own the referenced string. The string is considered const. A string referenced by StringRefNull can be expected to be null-terminated. That is not the case for StringRef. This commit is a continuation of rB369d5e8ad2bb7c2.
This commit is contained in:
parent
914f4308fb
commit
058d218254
244
source/blender/blenlib/BLI_string_ref.h
Normal file
244
source/blender/blenlib/BLI_string_ref.h
Normal file
@ -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
|
@ -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
|
||||
|
230
tests/gtests/blenlib/BLI_string_ref_test.cc
Normal file
230
tests/gtests/blenlib/BLI_string_ref_test.cc
Normal file
@ -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");
|
||||
}
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user