Commit 28a34a4e authored by Marcin Pawlowski's avatar Marcin Pawlowski Committed by Tudor Bosman

added replaceAt and replaceAll to MutableStringPiece

Summary:
To make MutableStringPiece more usable (and make the
implementation of replace* methods easier) I made sure that
all the const methods (like find) work for MutableStringPiece
in a fashion similar to how they work with StringPiece
(work with everything auto convertible to StringPiece). As
a result this change is pretty susbstatial in LOC, even
though now much happens here.

Test Plan: unit tests

Reviewed By: marcelo.juchem@fb.com

FB internal diff: D1420965

Tasks: 4632424
parent 24e54548
...@@ -144,6 +144,19 @@ public: ...@@ -144,6 +144,19 @@ public:
typename std::iterator_traits<Iter>::reference>::type typename std::iterator_traits<Iter>::reference>::type
value_type; value_type;
typedef typename std::iterator_traits<Iter>::reference reference; typedef typename std::iterator_traits<Iter>::reference reference;
/**
* For MutableStringPiece and MutableByteRange we define StringPiece
* and ByteRange as const_range_type (for everything else its just
* identity). We do that to enable operations such as find with
* args which are const.
*/
typedef typename std::conditional<
std::is_same<Iter, char*>::value
|| std::is_same<Iter, unsigned char*>::value,
Range<const value_type*>,
Range<Iter>>::type const_range_type;
typedef std::char_traits<typename std::remove_const<value_type>::type> typedef std::char_traits<typename std::remove_const<value_type>::type>
traits_type; traits_type;
...@@ -352,8 +365,12 @@ public: ...@@ -352,8 +365,12 @@ public:
fbstring fbstr() const { return fbstring(b_, size()); } fbstring fbstr() const { return fbstring(b_, size()); }
fbstring toFbstring() const { return fbstr(); } fbstring toFbstring() const { return fbstr(); }
// Works only for Range<const char*> const_range_type castToConst() const {
int compare(const Range& o) const { return const_range_type(*this);
};
// Works only for Range<const char*> (and Range<char*>)
int compare(const const_range_type& o) const {
const size_type tsize = this->size(); const size_type tsize = this->size();
const size_type osize = o.size(); const size_type osize = o.size();
const size_type msize = std::min(tsize, osize); const size_type msize = std::min(tsize, osize);
...@@ -427,70 +444,72 @@ public: ...@@ -427,70 +444,72 @@ public:
} }
// string work-alike functions // string work-alike functions
size_type find(Range str) const { size_type find(const_range_type str) const {
return qfind(*this, str); return qfind(castToConst(), str);
} }
size_type find(Range str, size_t pos) const { size_type find(const_range_type str, size_t pos) const {
if (pos > size()) return std::string::npos; if (pos > size()) return std::string::npos;
size_t ret = qfind(subpiece(pos), str); size_t ret = qfind(castToConst().subpiece(pos), str);
return ret == npos ? ret : ret + pos; return ret == npos ? ret : ret + pos;
} }
size_type find(Iter s, size_t pos, size_t n) const { size_type find(Iter s, size_t pos, size_t n) const {
if (pos > size()) return std::string::npos; if (pos > size()) return std::string::npos;
size_t ret = qfind(pos ? subpiece(pos) : *this, Range(s, n)); auto forFinding = castToConst();
size_t ret = qfind(
pos ? forFinding.subpiece(pos) : forFinding, const_range_type(s, n));
return ret == npos ? ret : ret + pos; return ret == npos ? ret : ret + pos;
} }
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
size_type find(const Iter s) const { size_type find(const Iter s) const {
return qfind(*this, Range(s)); return qfind(castToConst(), const_range_type(s));
} }
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
size_type find(const Iter s, size_t pos) const { size_type find(const Iter s, size_t pos) const {
if (pos > size()) return std::string::npos; if (pos > size()) return std::string::npos;
size_type ret = qfind(subpiece(pos), Range(s)); size_type ret = qfind(castToConst().subpiece(pos), const_range_type(s));
return ret == npos ? ret : ret + pos; return ret == npos ? ret : ret + pos;
} }
size_type find(value_type c) const { size_type find(value_type c) const {
return qfind(*this, c); return qfind(castToConst(), c);
} }
size_type rfind(value_type c) const { size_type rfind(value_type c) const {
return folly::rfind(*this, c); return folly::rfind(castToConst(), c);
} }
size_type find(value_type c, size_t pos) const { size_type find(value_type c, size_t pos) const {
if (pos > size()) return std::string::npos; if (pos > size()) return std::string::npos;
size_type ret = qfind(subpiece(pos), c); size_type ret = qfind(castToConst().subpiece(pos), c);
return ret == npos ? ret : ret + pos; return ret == npos ? ret : ret + pos;
} }
size_type find_first_of(Range needles) const { size_type find_first_of(const_range_type needles) const {
return qfind_first_of(*this, needles); return qfind_first_of(castToConst(), needles);
} }
size_type find_first_of(Range needles, size_t pos) const { size_type find_first_of(const_range_type needles, size_t pos) const {
if (pos > size()) return std::string::npos; if (pos > size()) return std::string::npos;
size_type ret = qfind_first_of(subpiece(pos), needles); size_type ret = qfind_first_of(castToConst().subpiece(pos), needles);
return ret == npos ? ret : ret + pos; return ret == npos ? ret : ret + pos;
} }
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
size_type find_first_of(Iter needles) const { size_type find_first_of(Iter needles) const {
return find_first_of(Range(needles)); return find_first_of(const_range_type(needles));
} }
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor // Works only for Range<(const) (unsigned) char*> which have Range(Iter) ctor
size_type find_first_of(Iter needles, size_t pos) const { size_type find_first_of(Iter needles, size_t pos) const {
return find_first_of(Range(needles), pos); return find_first_of(const_range_type(needles), pos);
} }
size_type find_first_of(Iter needles, size_t pos, size_t n) const { size_type find_first_of(Iter needles, size_t pos, size_t n) const {
return find_first_of(Range(needles, n), pos); return find_first_of(const_range_type(needles, n), pos);
} }
size_type find_first_of(value_type c) const { size_type find_first_of(value_type c) const {
...@@ -506,7 +525,7 @@ public: ...@@ -506,7 +525,7 @@ public:
* *
* Note: Call find() directly if the index is needed. * Note: Call find() directly if the index is needed.
*/ */
bool contains(const Range& other) const { bool contains(const const_range_type& other) const {
return find(other) != std::string::npos; return find(other) != std::string::npos;
} }
...@@ -522,8 +541,9 @@ public: ...@@ -522,8 +541,9 @@ public:
/** /**
* Does this Range start with another range? * Does this Range start with another range?
*/ */
bool startsWith(const Range& other) const { bool startsWith(const const_range_type& other) const {
return size() >= other.size() && subpiece(0, other.size()) == other; return size() >= other.size()
&& castToConst().subpiece(0, other.size()) == other;
} }
bool startsWith(value_type c) const { bool startsWith(value_type c) const {
return !empty() && front() == c; return !empty() && front() == c;
...@@ -532,8 +552,9 @@ public: ...@@ -532,8 +552,9 @@ public:
/** /**
* Does this Range end with another range? * Does this Range end with another range?
*/ */
bool endsWith(const Range& other) const { bool endsWith(const const_range_type& other) const {
return size() >= other.size() && subpiece(size() - other.size()) == other; return size() >= other.size()
&& castToConst().subpiece(size() - other.size()) == other;
} }
bool endsWith(value_type c) const { bool endsWith(value_type c) const {
return !empty() && back() == c; return !empty() && back() == c;
...@@ -543,7 +564,7 @@ public: ...@@ -543,7 +564,7 @@ public:
* Remove the given prefix and return true if the range starts with the given * Remove the given prefix and return true if the range starts with the given
* prefix; return false otherwise. * prefix; return false otherwise.
*/ */
bool removePrefix(const Range& prefix) { bool removePrefix(const const_range_type& prefix) {
return startsWith(prefix) && (b_ += prefix.size(), true); return startsWith(prefix) && (b_ += prefix.size(), true);
} }
bool removePrefix(value_type prefix) { bool removePrefix(value_type prefix) {
...@@ -554,13 +575,73 @@ public: ...@@ -554,13 +575,73 @@ public:
* Remove the given suffix and return true if the range ends with the given * Remove the given suffix and return true if the range ends with the given
* suffix; return false otherwise. * suffix; return false otherwise.
*/ */
bool removeSuffix(const Range& suffix) { bool removeSuffix(const const_range_type& suffix) {
return endsWith(suffix) && (e_ -= suffix.size(), true); return endsWith(suffix) && (e_ -= suffix.size(), true);
} }
bool removeSuffix(value_type suffix) { bool removeSuffix(value_type suffix) {
return endsWith(suffix) && (--e_, true); return endsWith(suffix) && (--e_, true);
} }
/**
* Replaces the content of the range, starting at position 'pos', with
* contents of 'replacement'. Entire 'replacement' must fit into the
* range. Returns false if 'replacements' does not fit. Example use:
*
* char in[] = "buffer";
* auto msp = MutablesStringPiece(input);
* EXPECT_TRUE(msp.replaceAt(2, "tt"));
* EXPECT_EQ(msp, "butter");
*
* // not enough space
* EXPECT_FALSE(msp.replace(msp.size() - 1, "rr"));
* EXPECT_EQ(msp, "butter"); // unchanged
*/
bool replaceAt(size_t pos, const_range_type replacement) {
if (size() < pos + replacement.size()) {
return false;
}
std::copy(replacement.begin(), replacement.end(), begin() + pos);
return true;
}
/**
* Replaces all occurences of 'source' with 'dest'. Returns number
* of replacements made. Source and dest have to have the same
* length. Throws if the lengths are different. If 'source' is a
* pattern that is overlapping with itself, we perform sequential
* replacement: "aaaaaaa".replaceAll("aa", "ba") --> "bababaa"
*
* Example use:
*
* char in[] = "buffer";
* auto msp = MutablesStringPiece(input);
* EXPECT_EQ(msp.replaceAll("ff","tt"), 1);
* EXPECT_EQ(msp, "butter");
*/
size_t replaceAll(const_range_type source, const_range_type dest) {
if (source.size() != dest.size()) {
throw std::invalid_argument(
"replacement must have the same size as source");
}
if (dest.empty()) {
return 0;
}
size_t pos = 0;
size_t num_replaced = 0;
size_type found = std::string::npos;
while ((found = find(source, pos)) != std::string::npos) {
replaceAt(found, dest);
pos += source.size();
++num_replaced;
}
return num_replaced;
}
/** /**
* Splits this `Range` `[b, e)` in the position `i` dictated by the next * Splits this `Range` `[b, e)` in the position `i` dictated by the next
* occurence of `delimiter`. * occurence of `delimiter`.
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <random>
#include <string> #include <string>
#include <sys/mman.h> #include <sys/mman.h>
#include <vector> #include <vector>
...@@ -1042,3 +1043,123 @@ TEST(RangeFunc, CArray) { ...@@ -1042,3 +1043,123 @@ TEST(RangeFunc, CArray) {
int x[] {1, 2, 3, 4}; int x[] {1, 2, 3, 4};
testRangeFunc(x, 4); testRangeFunc(x, 4);
} }
std::string get_rand_str(
int size, std::uniform_int_distribution<>& dist, std::mt19937& gen) {
std::string ret(size, '\0');
for (int i=0; i<size; ++i) {
ret[i] = static_cast<char>(dist(gen));
}
return ret;
}
namespace folly {
bool operator==(MutableStringPiece mp, StringPiece sp) {
return mp.compare(sp) == 0;
}
bool operator==(StringPiece sp, MutableStringPiece mp) {
return mp.compare(sp) == 0;
}
}
TEST(ReplaceAt, exhaustiveTest) {
char input[] = "this is nice and long input";
auto msp = MutableStringPiece(input);
auto str = std::string(input);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist('a', 'z');
for (int i=0; i < 100; ++i) {
for (int j = 1; j <= msp.size(); ++j) {
auto replacement = get_rand_str(j, dist, gen);
for (int pos=0; pos < msp.size() - j; ++pos) {
msp.replaceAt(pos, replacement);
str.replace(pos, replacement.size(), replacement);
EXPECT_EQ(msp.compare(str), 0);
}
}
}
// too far
EXPECT_EQ(msp.replaceAt(msp.size() - 2, StringPiece("meh")), false);
}
TEST(ReplaceAll, basicTest) {
char input[] = "this is nice and long input";
auto orig = std::string(input);
auto msp = MutableStringPiece(input);
EXPECT_EQ(msp.replaceAll("is", "si"), 2);
EXPECT_EQ("thsi si nice and long input", msp);
EXPECT_EQ(msp.replaceAll("si", "is"), 2);
EXPECT_EQ(msp, orig);
EXPECT_EQ(msp.replaceAll("abcd", "efgh"), 0); // nothing to replace
EXPECT_EQ(msp, orig);
// at the very beginning
EXPECT_EQ(msp.replaceAll("this", "siht"), 1);
EXPECT_EQ("siht is nice and long input", msp);
EXPECT_EQ(msp.replaceAll("siht", "this"), 1);
EXPECT_EQ(msp, orig);
// at the very end
EXPECT_EQ(msp.replaceAll("input", "soput"), 1);
EXPECT_EQ("this is nice and long soput", msp);
EXPECT_EQ(msp.replaceAll("soput", "input"), 1);
EXPECT_EQ(msp, orig);
// all spaces
EXPECT_EQ(msp.replaceAll(" ", "@"), 5);
EXPECT_EQ("this@is@nice@and@long@input", msp);
EXPECT_EQ(msp.replaceAll("@", " "), 5);
EXPECT_EQ(msp, orig);
}
TEST(ReplaceAll, randomTest) {
char input[] = "abcdefghijklmnoprstuwqz"; // no pattern repeata inside
auto orig = std::string(input);
auto msp = MutableStringPiece(input);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist('A', 'Z');
for (int i=0; i < 100; ++i) {
for (int j = 1; j <= orig.size(); ++j) {
auto replacement = get_rand_str(j, dist, gen);
for (int pos=0; pos < msp.size() - j; ++pos) {
auto piece = orig.substr(pos, j);
EXPECT_EQ(msp.replaceAll(piece, replacement), 1);
EXPECT_EQ(msp.find(replacement), pos);
EXPECT_EQ(msp.replaceAll(replacement, piece), 1);
EXPECT_EQ(msp, orig);
}
}
}
}
TEST(ReplaceAll, BadArg) {
int count = 0;
auto fst = "longer";
auto snd = "small";
char input[] = "meh meh meh";
auto all = MutableStringPiece(input);
try {
all.replaceAll(fst, snd);
} catch (std::invalid_argument&) {
++count;
}
try {
all.replaceAll(snd, fst);
} catch (std::invalid_argument&) {
++count;
}
EXPECT_EQ(count, 2);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment