From ec7a852582bd2363d7d53e16e3509294f4d6047c Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 5 Dec 2019 15:11:38 +0000 Subject: [PATCH] Refactor and improve rdcarray/rdcstr interface * We are going to replace all std::string/std::vector use with these. --- qrenderdoc/Code/pyrenderdoc/PythonContext.cpp | 2 +- .../PipelineState/PipelineStateViewer.cpp | 16 +- renderdoc/android/android.cpp | 2 +- renderdoc/api/replay/rdcarray.h | 125 ++++- renderdoc/api/replay/rdcstr.h | 243 +++++++++- renderdoc/replay/basic_types_tests.cpp | 455 +++++++++++++++++- 6 files changed, 802 insertions(+), 41 deletions(-) diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp index d5e9f97f5..f0981fca1 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp @@ -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; diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 561a572b0..d3040f226 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -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(), diff --git a/renderdoc/android/android.cpp b/renderdoc/android/android.cpp index 7155e3361..c05f11b41 100644 --- a/renderdoc/android/android.cpp +++ b/renderdoc/android/android.cpp @@ -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 activities; diff --git a/renderdoc/api/replay/rdcarray.h b/renderdoc/api/replay/rdcarray.h index 383f0aa5b..174068c76 100644 --- a/renderdoc/api/replay/rdcarray.h +++ b/renderdoc/api/replay/rdcarray.h @@ -27,6 +27,7 @@ #include // for standard types #include // for memcpy, etc +#include #include #include #include @@ -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::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 &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 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 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 &in) { *this = in; } inline void assign(const std::initializer_list &in) { *this = in; } @@ -632,6 +747,8 @@ struct bytebuf : public rdcarray { bytebuf() : rdcarray() {} bytebuf(const std::vector &in) : rdcarray(in) {} + bytebuf(const std::initializer_list &in) : rdcarray(in) {} + bytebuf(const byte *in, size_t size) : rdcarray(in, size) {} #if defined(RENDERDOC_QT_COMPAT) bytebuf(const QByteArray &in) { diff --git a/renderdoc/api/replay/rdcstr.h b/renderdoc/api/replay/rdcstr.h index 2fbadd8c1..0881bf6d4 100644 --- a/renderdoc/api/replay/rdcstr.h +++ b/renderdoc/api/replay/rdcstr.h @@ -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 diff --git a/renderdoc/replay/basic_types_tests.cpp b/renderdoc/replay/basic_types_tests.cpp index 1b19fa870..1df9156df 100644 --- a/renderdoc/replay/basic_types_tests.cpp +++ b/renderdoc/replay/basic_types_tests.cpp @@ -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(©Constructor); } + 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 test; @@ -240,7 +257,7 @@ TEST_CASE("Test array type", "[basictypes]") }; }; - SECTION("Verify insert()") + SECTION("insert") { rdcarray 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 largedata; + rdcarray 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 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 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 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 { + rdcarray 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 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 test2(test); + + CHECK(test.empty()); + CHECK(test2.empty()); + + rdcarray 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);