Refactor and improve rdcarray/rdcstr interface

* We are going to replace all std::string/std::vector use with these.
This commit is contained in:
baldurk
2019-12-05 15:11:38 +00:00
parent 8e21b28785
commit ec7a852582
6 changed files with 802 additions and 41 deletions
@@ -1138,7 +1138,7 @@ PyObject *PythonContext::outstream_write(PyObject *self, PyObject *args)
_frame *frame = PyEval_GetFrame();
while(message.back() == '\n' || message.back() == '\r')
message.erase(message.size() - 1);
message.pop_back();
QString filename = lit("unknown");
int line = 0;
@@ -1018,19 +1018,9 @@ QString PipelineStateViewer::GetVBufferFormatString(uint32_t slot)
uint32_t stride = vbs[slot].byteStride;
// filter attributes to only the ones enabled and using this slot
for(size_t i = 0; i < attrs.size();)
{
if(!attrs[i].used || attrs[i].vertexBuffer != (int)slot)
{
attrs.erase(i);
// continue with same i
}
else
{
// move to next i
i++;
}
}
attrs.removeIf([slot](const VertexInputAttribute &attr) {
return (!attr.used || attr.vertexBuffer != (int)slot);
});
// we now have all attributes in this buffer. Sort by offset
std::sort(attrs.begin(), attrs.end(),
+1 -1
View File
@@ -594,7 +594,7 @@ struct AndroidRemoteServer : public RemoteServer
rdcstr package = path;
if(!package.empty() && package[0] == '/')
package.erase(0);
package.erase(0, 1);
std::vector<PathEntry> activities;
+121 -4
View File
@@ -27,6 +27,7 @@
#include <stdint.h> // for standard types
#include <string.h> // for memcpy, etc
#include <functional>
#include <initializer_list>
#include <type_traits>
#include <vector>
@@ -150,6 +151,12 @@ public:
typedef T value_type;
rdcarray() : elems(NULL), allocatedCount(0), usedCount(0) {}
rdcarray(size_t count)
{
elems = NULL;
allocatedCount = usedCount = 0;
resize(count);
}
~rdcarray()
{
// clear will destruct the actual elements still existing
@@ -192,7 +199,22 @@ public:
size_t capacity() const { return allocatedCount; }
bool empty() const { return usedCount == 0; }
bool isEmpty() const { return usedCount == 0; }
void clear() { resize(0); }
void clear()
{
// we specialise clear() so that it doesn't implicitly require a default constructor of T()
// resize(0);
size_t sz = size();
if(sz == 0)
return;
setUsedCount(0);
// destroy the old items
ItemDestroyHelper<T>::destroyRange(elems, sz);
}
/////////////////////////////////////////////////////////////////
// managing elements and memory
@@ -267,8 +289,24 @@ public:
setUsedCount(usedCount + 1);
}
// fill the string with 'count' copies of 'el'
void fill(size_t count, const T &el)
{
// destruct any old elements
clear();
// ensure we have enough space for the count
reserve(count);
// copy-construct all elements in place and update space
for(size_t i = 0; i < count; i++)
new(elems + i) T(el);
setUsedCount(count);
}
void insert(size_t offs, const T *el, size_t count)
{
if(count == 0)
return;
if(elems < el + count && el < elems + allocatedCount)
{
// we're inserting from ourselves, so if we did this blindly we'd potentially change the
@@ -403,12 +441,21 @@ public:
}
// helpful shortcut for 'append at end', basically a multi-element push_back
inline void append(const T *el, size_t count) { insert(size(), el, count); }
inline void append(const rdcarray<T> &in) { insert(size(), in.data(), in.size()); }
void erase(size_t offs, size_t count = 1)
{
// invalid count
if(offs + count > size())
if(count == 0)
return;
const size_t sz = size();
// invalid offset
if(offs >= sz)
return;
if(count > sz - offs)
count = sz - offs;
// this is simpler to implement than insert(). We do two simpler passes:
//
// Pass 1: Iterate over the secified range, destruct it.
@@ -420,7 +467,7 @@ public:
elems[offs + i].~T();
// move remaining elements into place
for(size_t i = offs + count; i < size(); i++)
for(size_t i = offs + count; i < sz; i++)
{
new(elems + i - count) T(elems[i]);
elems[i].~T();
@@ -430,6 +477,12 @@ public:
setUsedCount(usedCount - count);
}
void pop_back()
{
if(!empty())
erase(size() - 1);
}
/////////////////////////////////////////////////////////////////
// Qt style helper functions
@@ -463,6 +516,35 @@ public:
erase((size_t)idx);
}
void removeIf(std::function<bool(const T &)> predicate)
{
for(size_t i = 0; i < size();)
{
if(predicate(at(i)))
{
erase(i);
// continue with same i
}
else
{
// move to next i
i++;
}
}
}
void removeOneIf(std::function<bool(const T &)> predicate)
{
for(size_t i = 0; i < size(); i++)
{
if(predicate(at(i)))
{
erase(i);
break;
}
}
}
/////////////////////////////////////////////////////////////////
// constructors that just forward to assign
rdcarray(const T *in, size_t count)
@@ -497,6 +579,39 @@ public:
std::swap(usedCount, other.usedCount);
}
// move operator/constructor using swap
rdcarray &operator=(rdcarray &&in)
{
// if we have old elems, clear (to destruct) and deallocate
if(elems)
{
clear();
deallocate(elems);
}
// set ourselves to a pristine state
elems = NULL;
allocatedCount = 0;
usedCount = 0;
// now swap with the incoming array, so it becomes empty
swap(in);
return *this;
}
rdcarray(rdcarray &&in)
{
// set ourselves to a pristine state
elems = NULL;
allocatedCount = 0;
usedCount = 0;
// now swap with the incoming array, so it becomes empty
swap(in);
}
// assign forwards to operator =
inline void assign(const std::vector<T> &in) { *this = in; }
inline void assign(const std::initializer_list<T> &in) { *this = in; }
@@ -632,6 +747,8 @@ struct bytebuf : public rdcarray<byte>
{
bytebuf() : rdcarray<byte>() {}
bytebuf(const std::vector<byte> &in) : rdcarray<byte>(in) {}
bytebuf(const std::initializer_list<byte> &in) : rdcarray<byte>(in) {}
bytebuf(const byte *in, size_t size) : rdcarray<byte>(in, size) {}
#if defined(RENDERDOC_QT_COMPAT)
bytebuf(const QByteArray &in)
{
+226 -17
View File
@@ -270,9 +270,19 @@ public:
return *this;
}
inline void swap(rdcstr &other)
{
// just need to swap the d element
std::swap(d, other.d);
}
// assign from an rdcstr, copy the d element and allocate if needed
void assign(const rdcstr &in)
{
// do nothing if we're self-assigning
if(this == &in)
return;
// if the input d is allocated, we need to make our own allocation. Go through the standard
// string assignment function which will allocate & copy
if(in.is_alloc())
@@ -314,7 +324,7 @@ public:
void append(const std::string &str) { append(str.c_str(), str.size()); }
void append(const rdcstr &str) { append(str.c_str(), str.size()); }
void append(const char *const str, size_t length) { insert(size(), str, length); }
void erase(size_t offs, size_t count = 1)
void erase(size_t offs, size_t count)
{
const size_t sz = size();
@@ -335,8 +345,24 @@ public:
void insert(size_t offset, const char *const str) { insert(offset, str, strlen(str)); }
void insert(size_t offset, const std::string &str) { insert(offset, str.c_str(), str.size()); }
void insert(size_t offset, const rdcstr &str) { insert(offset, str.c_str(), str.size()); }
void insert(size_t offset, char c) { insert(offset, &c, 1); }
void insert(size_t offset, const char *const instr, size_t length)
{
if(!is_fixed() && instr + length >= begin() && end() >= instr)
{
// we're inserting from ourselves and we're not allocated, so if we did this blindly
// we'd potentially change the contents of the inserted range while doing the insertion.
// To fix that, we store our original data in a temp and copy into ourselves again. Then we
// insert from the temp copy and let it be destroyed.
// This could be more efficient as an append and then a rotate, but this is simpler for now.
rdcstr copy;
copy.swap(*this);
this->reserve(copy.capacity() + length);
*this = copy;
insert(offset, copy.c_str(), copy.size());
return;
}
const size_t sz = size();
// invalid offset
@@ -361,6 +387,19 @@ public:
d.arr.set_size(sz + length);
}
void replace(size_t offset, size_t length, const rdcstr &str)
{
erase(offset, length);
insert(offset, str);
}
// fill the string with 'count' copies of 'c'
void fill(size_t count, char c)
{
resize(count);
memset(data(), c, count);
}
// cast operators
operator std::string() const
{
@@ -369,7 +408,7 @@ public:
}
// read-only by-value accessor can look up directly in c_str() since it can't be modified
char operator[](size_t i) const { return c_str()[i]; }
const char &operator[](size_t i) const { return c_str()[i]; }
// assignment operator must make the string mutable first
char &operator[](size_t i)
{
@@ -540,6 +579,8 @@ public:
}
const char *begin() const { return c_str(); }
const char *end() const { return c_str() + size(); }
char *begin() { return data(); }
char *end() { return data() + size(); }
char front() const { return *c_str(); }
char &front()
{
@@ -553,7 +594,7 @@ public:
return data()[size() - 1];
}
rdcstr substr(size_t offs, size_t length = ~0U)
rdcstr substr(size_t offs, size_t length = ~0U) const
{
const size_t sz = size();
if(offs >= sz)
@@ -580,6 +621,11 @@ public:
append(str.c_str(), str.size());
return *this;
}
rdcstr &operator+=(char c)
{
push_back(c);
return *this;
}
rdcstr operator+(const char *const str) const
{
rdcstr ret = *this;
@@ -598,24 +644,36 @@ public:
ret += str;
return ret;
}
rdcstr operator+(const char c) const
{
rdcstr ret = *this;
ret += c;
return ret;
}
// Qt-type interface
bool isEmpty() const { return size() == 0; }
int32_t count() const { return (int32_t)size(); }
char takeAt(size_t offs)
char takeAt(int32_t offs)
{
char ret = c_str()[offs];
erase(offs);
erase(offs, 1);
return ret;
}
// Python interface
int32_t indexOf(char el, size_t first = 0, size_t last = ~0U) const
int32_t indexOf(char el, int32_t first = 0, int32_t last = -1) const
{
const char *str = c_str();
const size_t sz = size();
if(first < 0)
return -1;
for(size_t i = first; i < sz && i < last; i++)
const char *str = c_str();
size_t sz = size();
if(last >= 0 && (size_t)last < sz)
sz = last;
for(size_t i = first; i < sz; i++)
{
if(str[i] == el)
return (int32_t)i;
@@ -624,18 +682,32 @@ public:
return -1;
}
int32_t find(const char *needle_str, size_t needle_len, size_t first = 0, size_t last = ~0U) const
// find a substring. Optionally starting at a given 'first' character and not including an
// optional 'last' character. If last is -1 (the default), the whole string is searched
int32_t find(const char *needle_str, size_t needle_len, int32_t first, int32_t last) const
{
// no default parameters for first/last here because otherwise it'd be dangerous with casting
// and
// misusing this instead of just specifying a 'first' below.
const char *haystack = c_str();
const size_t haystack_len = size();
size_t haystack_len = size();
if(needle_len > haystack_len)
if(first < 0)
return -1;
if(needle_len == 0)
return 0;
for(size_t i = first; i <= haystack_len - needle_len && i < last; i++)
if(last >= 0 && (size_t)last < haystack_len)
haystack_len = last;
if((size_t)first >= haystack_len)
return -1;
if(needle_len > haystack_len - first)
return -1;
for(size_t i = first; i <= haystack_len - needle_len; i++)
{
if(strncmp(haystack + i, needle_str, needle_len) == 0)
return (int32_t)i;
@@ -644,28 +716,165 @@ public:
return -1;
}
int32_t find(const rdcstr &needle, size_t first = 0, size_t last = ~0U) const
int32_t find(const char needle, int32_t first = 0, int32_t last = -1) const
{
if(first < 0)
return -1;
const char *haystack = c_str();
size_t haystack_len = size();
if(last >= 0 && (size_t)last < haystack_len)
haystack_len = last;
for(size_t i = first; i < haystack_len; i++)
{
if(haystack[i] == needle)
return (int32_t)i;
}
return -1;
}
int32_t find(const rdcstr &needle, int32_t first = 0, int32_t last = -1) const
{
return find(needle.c_str(), needle.size(), first, last);
}
int32_t find(const std::string &needle, size_t first = 0, size_t last = ~0U) const
int32_t find(const std::string &needle, int32_t first = 0, int32_t last = -1) const
{
return find(needle.c_str(), needle.size(), first, last);
}
int32_t find(const char *needle, size_t first = 0, size_t last = ~0U) const
int32_t find(const char *needle, int32_t first = 0, int32_t last = -1) const
{
return find(needle, strlen(needle), first, last);
}
// find the first character that is in a given set of characters, from the start of the string
// Optionally starting at a given 'first' character and not including an optional 'last'
// character. If last is -1 (the default), the whole string is searched
int32_t find_first_of(const rdcstr &needle_set, int32_t first = 0, int32_t last = -1) const
{
return find_first_last(needle_set, true, true, first, last);
}
// find the first character that is not in a given set of characters, from the start of the string
int32_t find_first_not_of(const rdcstr &needle_set, int32_t first = 0, int32_t last = -1) const
{
return find_first_last(needle_set, true, false, first, last);
}
// find the first character that is in a given set of characters, from the end of the string
int32_t find_last_of(const rdcstr &needle_set, int32_t first = 0, int32_t last = -1) const
{
return find_first_last(needle_set, false, true, first, last);
}
// find the first character that is not in a given set of characters, from the end of the string
int32_t find_last_not_of(const rdcstr &needle_set, int32_t first = 0, int32_t last = -1) const
{
return find_first_last(needle_set, false, false, first, last);
}
private:
int32_t find_first_last(const rdcstr &needle_set, bool forward_search, bool search_in_set,
int32_t first, int32_t last) const
{
if(first < 0)
return -1;
const char *haystack = c_str();
size_t haystack_len = size();
if(last >= 0 && (size_t)last < haystack_len)
haystack_len = last;
if(forward_search)
{
for(size_t i = first; i < haystack_len; i++)
{
bool in_set = needle_set.contains(haystack[i]);
if(in_set == search_in_set)
return (int32_t)i;
}
}
else
{
for(size_t i = 0; i < haystack_len; i++)
{
size_t idx = haystack_len - 1 - i;
if(idx < (size_t)first)
break;
bool in_set = needle_set.contains(haystack[idx]);
if(in_set == search_in_set)
return (int32_t)idx;
}
}
return -1;
}
public:
bool contains(char needle) const { return indexOf(needle) != -1; }
bool contains(const rdcstr &needle) const { return find(needle) != -1; }
bool contains(const std::string &needle) const { return find(needle) != -1; }
bool contains(const char *needle) const { return find(needle) != -1; }
bool beginsWith(const rdcstr &beginning) const
{
if(beginning.length() > length())
return false;
return !strncmp(c_str(), beginning.c_str(), beginning.length());
}
bool endsWith(const rdcstr &ending) const
{
if(ending.length() > length())
return false;
return !strcmp(c_str() + length() - ending.length(), ending.c_str());
}
void removeOne(char el)
{
int idx = indexOf(el);
if(idx >= 0)
erase((size_t)idx);
erase((size_t)idx, 1);
}
// remove any preceeding or trailing whitespace from the string, in-place
void trim()
{
if(empty())
return;
#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n')
const char *str = c_str();
size_t sz = size();
size_t start = 0;
while(start < sz && IS_WHITESPACE(str[start]))
start++;
size_t end = sz - 1;
while(end > start && IS_WHITESPACE(str[end]))
end--;
// no non-whitespace characters, become the empty string
if(start >= end)
{
clear();
return;
}
erase(end + 1, ~0U);
erase(0, start);
}
// return a copy of the string with preceeding and trailing whitespace removed
rdcstr trimmed() const
{
rdcstr ret = *this;
ret.trim();
return ret;
}
// for equality check with rdcstr, check quickly for empty string comparisons
+450 -5
View File
@@ -32,6 +32,7 @@
#include "3rdparty/catch/catch.hpp"
static volatile int32_t constructor = 0;
static volatile int32_t moveConstructor = 0;
static volatile int32_t valueConstructor = 0;
static volatile int32_t copyConstructor = 0;
static volatile int32_t destructor = 0;
@@ -55,11 +56,27 @@ struct ConstructorCounter
value = other.value;
Atomic::Inc32(&copyConstructor);
}
ConstructorCounter(ConstructorCounter &&other)
{
value = other.value;
other.value = -9999;
Atomic::Inc32(&moveConstructor);
}
ConstructorCounter &operator=(const ConstructorCounter &other) = delete;
ConstructorCounter &operator=(ConstructorCounter &&other) = delete;
~ConstructorCounter() { Atomic::Inc32(&destructor); }
bool operator==(const ConstructorCounter &other) { return value == other.value; }
};
TEST_CASE("Test array type", "[basictypes]")
{
// reset globals
constructor = 0;
moveConstructor = 0;
valueConstructor = 0;
copyConstructor = 0;
destructor = 0;
SECTION("Basic test")
{
rdcarray<int> test;
@@ -240,7 +257,7 @@ TEST_CASE("Test array type", "[basictypes]")
};
};
SECTION("Verify insert()")
SECTION("insert")
{
rdcarray<int> vec;
vec.insert(0, {});
@@ -304,7 +321,7 @@ TEST_CASE("Test array type", "[basictypes]")
CHECK(vec[8] == 4);
// insert a large amount of data to ensure this doesn't read off start/end of vector
std::vector<int> largedata;
rdcarray<int> largedata;
largedata.resize(100000);
vec.insert(4, largedata);
@@ -353,7 +370,7 @@ TEST_CASE("Test array type", "[basictypes]")
CHECK(vec[14] == 16);
};
SECTION("Verify erase()")
SECTION("erase")
{
rdcarray<int> vec = {6, 3, 13, 5};
@@ -408,6 +425,69 @@ TEST_CASE("Test array type", "[basictypes]")
CHECK(vec[0] == 5);
CHECK(vec[1] == 6);
CHECK(vec[2] == 3);
vec = {5, 6, 3, 9, 1, 0};
vec.erase(3, 100);
REQUIRE(vec.size() == 3);
CHECK(vec[0] == 5);
CHECK(vec[1] == 6);
CHECK(vec[2] == 3);
};
SECTION("removeOne / removeOneIf / removeIf")
{
rdcarray<int> vec = {6, 3, 9, 6, 6, 3, 5, 15, 5};
vec.removeOne(3);
REQUIRE(vec.size() == 8);
CHECK(vec[0] == 6);
CHECK(vec[1] == 9);
CHECK(vec[2] == 6);
CHECK(vec[3] == 6);
CHECK(vec[4] == 3);
CHECK(vec[5] == 5);
CHECK(vec[6] == 15);
CHECK(vec[7] == 5);
vec.removeOne(3);
REQUIRE(vec.size() == 7);
CHECK(vec[0] == 6);
CHECK(vec[1] == 9);
CHECK(vec[2] == 6);
CHECK(vec[3] == 6);
CHECK(vec[4] == 5);
CHECK(vec[5] == 15);
CHECK(vec[6] == 5);
vec.removeOne(3);
REQUIRE(vec.size() == 7);
CHECK(vec[0] == 6);
CHECK(vec[1] == 9);
CHECK(vec[2] == 6);
CHECK(vec[3] == 6);
CHECK(vec[4] == 5);
CHECK(vec[5] == 15);
CHECK(vec[6] == 5);
vec.removeOneIf([](const int &el) { return (el % 3) == 0; });
REQUIRE(vec.size() == 6);
CHECK(vec[0] == 9);
CHECK(vec[1] == 6);
CHECK(vec[2] == 6);
CHECK(vec[3] == 5);
CHECK(vec[4] == 15);
CHECK(vec[5] == 5);
vec.removeIf([](const int &el) { return (el % 3) == 0; });
REQUIRE(vec.size() == 2);
CHECK(vec[0] == 5);
CHECK(vec[1] == 5);
};
SECTION("Check construction")
@@ -415,6 +495,7 @@ TEST_CASE("Test array type", "[basictypes]")
rdcarray<ConstructorCounter> test;
CHECK(constructor == 0);
CHECK(moveConstructor == 0);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 0);
CHECK(destructor == 0);
@@ -489,6 +570,89 @@ TEST_CASE("Test array type", "[basictypes]")
CHECK(constructor == 50);
CHECK(valueConstructor == 1);
CHECK(copyConstructor == 3);
// still should have had no moves
CHECK(moveConstructor == 0);
// reset counters
constructor = 0;
valueConstructor = 0;
copyConstructor = 0;
destructor = 0;
CHECK(constructor == 0);
CHECK(moveConstructor == 0);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 0);
CHECK(destructor == 0);
auto lambda = []() -> rdcarray<ConstructorCounter> {
rdcarray<ConstructorCounter> ret;
ConstructorCounter tmp(9);
ret.push_back(tmp);
return ret;
};
test = lambda();
// ensure that the value constructor was called only once within the lambda
CHECK(valueConstructor == 1);
// the copy constructor was called in push_back
CHECK(copyConstructor == 1);
// the destructor was called once for tmp
CHECK(destructor == 1);
// and no default construction or moves
CHECK(constructor == 0);
CHECK(moveConstructor == 0);
// check that the new value arrived
CHECK(test.back().value == 9);
};
SECTION("operations with empty array")
{
rdcarray<ConstructorCounter> test;
ConstructorCounter val;
test.append(test);
CHECK(test.empty());
test.insert(0, test);
CHECK(test.empty());
test.removeOne(val);
CHECK(test.empty());
test.assign(test.data(), test.size());
CHECK(test.empty());
CHECK(test.indexOf(val) == -1);
rdcarray<ConstructorCounter> test2(test);
CHECK(test.empty());
CHECK(test2.empty());
rdcarray<ConstructorCounter> test3;
test3 = test;
CHECK(test.empty());
CHECK(test3.empty());
CHECK(constructor == 1);
CHECK(moveConstructor == 0);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 0);
CHECK(destructor == 0);
};
SECTION("Inserting from array into itself")
@@ -775,6 +939,36 @@ TEST_CASE("Test string type", "[basictypes][string]")
lambda(STRING_LITERAL(LARGE_STRING), LARGE_STRING);
};
SECTION("Inserting from string into itself")
{
auto lambda = [](rdcstr test) {
// create a version without doing self-insertion
rdcstr test2 = test;
test2.insert(4, test);
test.insert(4, test);
CHECK(test.size() == test2.size());
CHECK(test.empty() == test2.empty());
for(size_t i = 0; i < test.size(); i++)
{
CHECK(test[i] == test2[i]);
}
CHECK(test.c_str() != test2.c_str());
};
// need a small string small enough that even doubling it is still small
lambda("foo");
lambda(SMALL_STRING);
lambda(LARGE_STRING);
lambda(VERY_LARGE_STRING);
lambda(STRING_LITERAL(LARGE_STRING));
};
SECTION("Shrinking and expanding strings")
{
rdcstr test = "A longer string that would have been heap allocated";
@@ -851,7 +1045,11 @@ TEST_CASE("Test string type", "[basictypes][string]")
{
rdcstr test = "Hello, World! This is a test string";
test.erase(0);
test.erase(0, 0);
CHECK(test == "Hello, World! This is a test string");
test.erase(0, 1);
CHECK(test == "ello, World! This is a test string");
@@ -867,7 +1065,7 @@ TEST_CASE("Test string type", "[basictypes][string]")
CHECK(test == ", World! is a ");
test.erase(100);
test.erase(100, 1);
CHECK(test == ", World! is a ");
@@ -899,6 +1097,13 @@ TEST_CASE("Test string type", "[basictypes][string]")
test2 += ", " + test + "?";
CHECK(test2 == "Hello World! And enough characters to force an allocation, Hello World?");
test += '?';
CHECK(test == "Hello World?");
test2 = test + '!';
CHECK(test2 == "Hello World?!");
};
SECTION("insert")
@@ -918,6 +1123,77 @@ TEST_CASE("Test string type", "[basictypes][string]")
test2.insert(100, "foo");
CHECK(test2 == "Hello, World!Hello, World!");
test2.insert(4, '_');
CHECK(test2 == "Hell_o, World!Hello, World!");
};
SECTION("replace")
{
rdcstr test = "Hello, World!";
test.replace(5, 1, ".");
CHECK(test == "Hello. World!");
test.replace(7, 3, "Fau");
CHECK(test == "Hello. Fauld!");
test.replace(0, 0, "Hi! ");
CHECK(test == "Hi! Hello. Fauld!");
test.replace(0, 99, "Test");
CHECK(test == "Test");
test.replace(2, 99, "sting!");
CHECK(test == "Testing!");
test.replace(20, 99, "Invalid?");
CHECK(test == "Testing!");
};
SECTION("beginsWith / endsWith")
{
rdcstr test = "foobar";
CHECK_FALSE(test.beginsWith("bar"));
CHECK(test.beginsWith("foo"));
CHECK(test.beginsWith(""));
CHECK(test.endsWith("bar"));
CHECK_FALSE(test.endsWith("foo"));
CHECK(test.endsWith(""));
test = "";
CHECK(test.endsWith(""));
CHECK_FALSE(test.endsWith("foo"));
CHECK(test.beginsWith(""));
CHECK_FALSE(test.beginsWith("foo"));
test = "bar";
CHECK_FALSE(test.beginsWith("foobar"));
CHECK_FALSE(test.endsWith("foobar"));
};
SECTION("trim / trimmed")
{
CHECK(rdcstr(" foo bar ").trimmed() == "foo bar");
CHECK(rdcstr(" Foo bar").trimmed() == "Foo bar");
CHECK(rdcstr(" Foo\nbar").trimmed() == "Foo\nbar");
CHECK(rdcstr("FOO BAR ").trimmed() == "FOO BAR");
CHECK(rdcstr("FOO BAR \t\n").trimmed() == "FOO BAR");
CHECK(rdcstr("").trimmed() == "");
CHECK(rdcstr(" ").trimmed() == "");
CHECK(rdcstr(" \t \n ").trimmed() == "");
};
SECTION("push_back and pop_back")
@@ -1025,6 +1301,7 @@ TEST_CASE("Test string type", "[basictypes][string]")
CHECK(test.find("Hello, World!!") == -1);
CHECK(test.find("Hello, World?") == -1);
CHECK(test.find("") == 0);
CHECK(test.find(',') == 5);
CHECK(test.indexOf('H') == 0);
CHECK(test.indexOf('l') == 2);
@@ -1047,8 +1324,176 @@ TEST_CASE("Test string type", "[basictypes][string]")
CHECK_FALSE(test.contains('!'));
CHECK(test == "ello, World");
CHECK(test.find_first_of("lo") == 1);
CHECK(test.find_first_of("ol") == 1);
CHECK(test.find_first_of("foobarl") == 1);
CHECK(test.find_first_of("foobar") == 3);
CHECK(test.find_first_of("oforab") == 3);
CHECK(test.find_first_of("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!o") == 3);
CHECK(test.find_first_of("!?$") == -1);
CHECK(test.find_first_of("") == -1);
CHECK(test.find_last_of("or") == 8);
CHECK(test.find_last_of("ro") == 8);
CHECK(test.find_last_of("foobard") == 10);
CHECK(test.find_last_of("foobar") == 8);
CHECK(test.find_last_of("oforab") == 8);
CHECK(test.find_last_of("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!r") == 8);
CHECK(test.find_last_of("!?$") == -1);
CHECK(test.find_last_of("") == -1);
CHECK(test.find_first_not_of("oel") == 4);
CHECK(test.find_first_not_of("le") == 3);
CHECK(test.find_first_not_of("oelWr") == 4);
CHECK(test.find_first_not_of("ooollele") == 4);
CHECK(test.find_first_not_of("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!e") == 1);
CHECK(test.find_first_not_of("!?$") == 0);
CHECK(test.find_first_not_of("") == 0);
CHECK(test.find_first_not_of("W, rdleo") == -1);
CHECK(test.find_last_not_of("dl") == 8);
CHECK(test.find_last_not_of("ld") == 8);
CHECK(test.find_last_not_of("ldWr") == 7);
CHECK(test.find_last_not_of("WWrldlRw") == 7);
CHECK(test.find_last_not_of("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!d") == 9);
CHECK(test.find_last_not_of("!?$") == 10);
CHECK(test.find_last_not_of("") == 10);
CHECK(test.find_last_not_of("W, rdleo") == -1);
// 0 1 2 3
// 012345678901234567890123456789012345678
test = "Test of substring matches and sub-find!";
// test with first before of the first 'sub'
CHECK(test.find("sub") == 8);
CHECK(test.find("sub", 4) == 8);
CHECK(test.find("sub", 8) == 8);
// first after first and before second
CHECK(test.find("sub", 9) == 30);
CHECK(test.find("sub", 10) == 30);
CHECK(test.find("sub", 11) == 30);
CHECK(test.find("sub", 29) == 30);
CHECK(test.find("sub", 30) == 30);
// first past the second
CHECK(test.find("sub", 31) == -1);
// first past the end of the string
CHECK(test.find("sub", 40) == -1);
// first and last around first sub
CHECK(test.find("sub", 4, 12) == 8);
CHECK(test.find("sub", 4, 11) == 8);
CHECK(test.find("sub", 7, 11) == 8);
CHECK(test.find("sub", 8, 11) == 8);
// first before but last not including first sub
CHECK(test.find("sub", 4, 9) == -1);
CHECK(test.find("sub", 8, 10) == -1);
CHECK(test.find("sub", 8, 9) == -1);
// first after and last after
CHECK(test.find("sub", 9, 11) == -1);
// empty range
CHECK(test.find("sub", 9, 9) == -1);
CHECK(test.find("sub", 8, 8) == -1);
// invalid range
CHECK(test.find("sub", 10, 9) == -1);
CHECK(test.find("find!") == 34);
CHECK(test.find("find!", 30, 39) == 34);
CHECK(test.find("find!", 30, 38) == -1);
CHECK(test.find('s') == 2);
CHECK(test.find('s', 2) == 2);
CHECK(test.find('s', 5) == 8);
CHECK(test.find('s', 2, 3) == 2);
CHECK(test.find('s', 2, 2) == -1);
CHECK(test.find('s', 3, 2) == -1);
CHECK(test.find('!') == 38);
CHECK(test.find('!', 38) == 38);
CHECK(test.find('!', 38) == 38);
CHECK(test.find('!', 38, 39) == 38);
CHECK(test.find('!', 38, 38) == -1);
CHECK(test.find('!', 39, 38) == -1);
CHECK(test.find_first_of("sx!") == 2);
CHECK(test.find_first_of("sx!", 2) == 2);
CHECK(test.find_first_of("sx!", 5) == 8);
CHECK(test.find_first_of("sx!", 2, 3) == 2);
CHECK(test.find_first_of("sx!", 2, 2) == -1);
CHECK(test.find_first_of("sx!", 3, 2) == -1);
CHECK(test.find_first_not_of("Teot") == 2);
CHECK(test.find_first_not_of("Teot", 2) == 2);
CHECK(test.find_first_not_of("Teot", 5) == 6);
CHECK(test.find_first_not_of("Teot", 2, 3) == 2);
CHECK(test.find_first_not_of("Teot", 2, 2) == -1);
CHECK(test.find_first_not_of("Teot", 3, 2) == -1);
CHECK(test.find_last_of("pur") == 31);
CHECK(test.find_last_of("pur", 30) == 31);
CHECK(test.find_last_of("pur", 0, 30) == 13);
CHECK(test.find_last_of("pur", 0, 31) == 13);
CHECK(test.find_last_of("pur", 0, 32) == 31);
CHECK(test.find_last_of("pur", 5) == 31);
CHECK(test.find_last_of("pur", 0, 5) == -1);
CHECK(test.find_last_of("pur", 10, 15) == 13);
CHECK(test.find_last_of("pur", 13, 15) == 13);
CHECK(test.find_last_of("pur", 14, 15) == -1);
CHECK(test.find_last_of("pur", 10, 13) == -1);
CHECK(test.find_last_of("pur", 13, 13) == -1);
CHECK(test.find_last_of("pur", 15, 13) == -1);
CHECK(test.find_last_not_of("ibe!fonudsc") == 33);
CHECK(test.find_last_not_of("ibe!fonudsc", 20) == 33);
CHECK(test.find_last_not_of("ibe!fonudsc", 20, 35) == 33);
CHECK(test.find_last_not_of("ibe!fonudsc", 20, 33) == 29);
CHECK(test.find_last_not_of("ibe!fonudsc", 29, 33) == 29);
CHECK(test.find_last_not_of("ibe!fonudsc", 29, 30) == 29);
CHECK(test.find_last_not_of("ibe!fonudsc", 29, 29) == -1);
CHECK(test.find_last_not_of("ibe!fonudsc", 30, 29) == -1);
};
SECTION("Comparisons")
{
rdcstr a = "Hello, World!";
rdcstr b = "Hello, World!";
CHECK_FALSE(a < b);
CHECK(a == b);
CHECK_FALSE(a > b);
b.back() = '?';
CHECK(a < b);
CHECK_FALSE(a == b);
CHECK_FALSE(a > b);
CHECK_FALSE(b < a);
CHECK_FALSE(b == a);
CHECK(b > a);
b[1] = 'a';
a.pop_back();
CHECK_FALSE(a < b);
CHECK_FALSE(a == b);
CHECK(a > b);
b[1] = 'e';
CHECK(a < b);
CHECK_FALSE(a == b);
CHECK_FALSE(a > b);
}
SECTION("String literal tests")
{
rdcstr test = STRING_LITERAL(LARGE_STRING);