pymol::zstring_view

This commit is contained in:
Thomas Holder
2019-06-07 16:22:08 +02:00
parent 60e2be61ec
commit 55135c8d04
5 changed files with 539 additions and 2 deletions

View File

@@ -0,0 +1,260 @@
/**
* pymol::zstring_view
* pymol::null_safe_zstring_view
*
* (c) Schrodinger, Inc.
*/
#pragma once
#include <cassert>
#include <cstring>
#include <ostream>
namespace pymol
{
/**
* Describes a null-terminated read-only string.
*
* The length is not stored. Calling `size()` or `end()` calculates the length
* on the fly in `O(size())`. All other methods behave exactly like
* `std::string_view` with the same complexity.
*
* Constructing from a nullptr is undefined. See `null_safe_zstring_view` for a
* null-safe version.
*/
class zstring_view
{
typedef char CharT;
const CharT* m_data = nullptr;
public:
typedef const CharT* const_pointer;
typedef const CharT* const_iterator;
typedef const CharT& const_reference;
typedef size_t size_type;
enum : size_type { npos = size_type(-1) };
constexpr zstring_view() = default;
constexpr zstring_view(const CharT* s) : m_data(s) {}
/**
* Construct from any type which has a `c_str()` method
*/
template <typename T, typename = decltype(&T::c_str)>
constexpr zstring_view(const T& t) : m_data(t.c_str())
{
}
constexpr const_iterator begin() const noexcept { return m_data; }
/**
* Like `std::string_view::end` but has complexity `O(size())`
*/
const_iterator end() const noexcept { return m_data + size(); }
constexpr const_reference operator[](size_type pos) const
{
return m_data[pos];
}
constexpr const_pointer data() const noexcept { return m_data; }
constexpr const_pointer c_str() const noexcept { return m_data; }
/**
* True if not viewing a NULL pointer
*/
constexpr explicit operator bool() const noexcept { return m_data; }
/**
* Position of the null-terminator. Complexity `O(size())`
*/
size_type size() const noexcept { return std::strlen(m_data); }
constexpr bool empty() const noexcept { return !m_data[0]; }
void remove_prefix(size_type n) { m_data += n; }
// SKIP swap
size_type copy(CharT* dest, size_type count, size_type pos = 0) const
{
assert(pos <= size()); // std::out_of_range with std::string_view
size_type i = 0;
for (; i != count && m_data[i + pos]; ++i)
dest[i] = m_data[i + pos];
return i;
}
constexpr zstring_view substr(size_type pos) const noexcept
{
return m_data + pos;
}
int compare(zstring_view v) const noexcept
{
return std::strcmp(m_data, v.m_data);
}
bool starts_with(zstring_view x) const noexcept
{
for (auto it_x = x.begin(), it = begin(); *it_x; ++it_x, ++it) {
if (*it_x != *it)
return false;
}
return true;
}
constexpr bool starts_with(CharT x) const noexcept { return m_data[0] == x; }
bool ends_with(zstring_view x) const noexcept
{
auto n = size(), n_x = x.size();
return n >= n_x && std::strcmp(m_data + n - n_x, x.m_data) == 0;
}
bool ends_with(CharT x) const noexcept
{
auto n = size();
return n > 0 && m_data[n - 1] == x;
}
size_type find(zstring_view v, size_type pos = 0) const noexcept
{
const_pointer p = std::strstr(m_data + pos, v.m_data);
return p ? p - m_data : npos;
}
size_type find(CharT ch, size_type pos = 0) const noexcept
{
const_pointer p = ch ? std::strchr(m_data + pos, ch) : nullptr;
return p ? p - m_data : npos;
}
size_type find_first_of(zstring_view v, size_type pos = 0) const noexcept
{
for (auto p = m_data + pos; *p; ++p) {
if (std::strchr(v.m_data, *p) != nullptr)
return p - m_data;
}
return npos;
}
size_type find_first_of(CharT ch, size_type pos = 0) const noexcept
{
return find(ch, pos);
}
size_type find_first_not_of(zstring_view v, size_type pos = 0) const noexcept
{
for (auto p = m_data + pos; *p; ++p) {
if (std::strchr(v.m_data, *p) == nullptr)
return p - m_data;
}
return npos;
}
size_type find_first_not_of(CharT ch, size_type pos = 0) const noexcept
{
for (auto p = m_data + pos; *p; ++p) {
if (ch != *p)
return p - m_data;
}
return npos;
}
// SKIP find_last_of
// SKIP find_last_not_of
};
inline bool operator==(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) == 0;
}
inline bool operator!=(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) != 0;
}
inline bool operator<(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) < 0;
}
inline bool operator>(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) > 0;
}
inline bool operator<=(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) <= 0;
}
inline bool operator>=(zstring_view lhs, zstring_view rhs) noexcept
{
return lhs.compare(rhs) >= 0;
}
/**
* Describes a null-terminated read-only string which is never nullptr. If
* constructred from a nullptr, it will point to an empty string instead.
*/
class null_safe_zstring_view : public zstring_view
{
public:
/**
* Construct from the empty string.
*/
constexpr null_safe_zstring_view() : zstring_view("") {}
/**
* Copy constructor
*/
constexpr null_safe_zstring_view(const null_safe_zstring_view& v)
: zstring_view(v.data())
{
}
/**
* Construct from a null-terminated character string or from the empty string
* if `s` is nullptr.
*/
constexpr null_safe_zstring_view(const char* s) : zstring_view(s ? s : "") {}
/**
* Construct from any type which has a `c_str()` method under the assumption
* that `c_str()` never returns nullptr. Assumption is not true for
* `zstring_view`, see special overload.
*/
template <typename T, typename = decltype(&T::c_str)>
constexpr null_safe_zstring_view(const T& t) : zstring_view(t.c_str())
{
}
/**
* Construct from a `zstring_view` or from the empty string if `v` points to
* nullptr.
*/
constexpr null_safe_zstring_view(zstring_view v)
: zstring_view(v.data() ? v.data() : "")
{
}
};
} // namespace pymol
namespace std
{
inline ostream& operator<<(ostream& os, pymol::zstring_view v)
{
os << v.c_str();
return os;
}
template <> struct hash<pymol::zstring_view> {
size_t operator()(pymol::zstring_view v) const
{
// OVLexicon hash function (derived from djb2)
size_t i = 0;
size_t hashval = v[0] << 7;
for (; v[i]; ++i) {
hashval += (hashval << 5) + v[i];
}
return hashval ^ i;
}
};
} // namespace std

View File

@@ -38,6 +38,7 @@ Z* -------------------------------------------------------------------
#include"Setting.h"
#include"Executive.h"
#include "Lex.h"
#include "pymol/zstring_view.h"
#include <map>
@@ -2367,7 +2368,7 @@ int AtomInfoGetExpectedValence(PyMOLGlobals * G, const AtomInfoType * I)
static int get_protons(const char * symbol)
{
char titleized[4];
static std::map<const char *, int, cstrless_t> lookup;
static std::map<pymol::zstring_view, int> lookup;
if (lookup.empty()) {
for (int i = 0; i < ElementTableSize; i++)

View File

@@ -33,6 +33,7 @@
#include "Vector.h"
#include "Lex.h"
#include "strcasecmp.h"
#include "pymol/zstring_view.h"
// canonical amino acid three letter codes
const char * aa_three_letter[] = {
@@ -765,7 +766,7 @@ static bool read_pdbx_coordinate_model(PyMOLGlobals * G, cif_data * data, Object
return false;
// affected chains
std::set<const char*, strless2_t> asyms;
std::set<pymol::zstring_view> asyms;
// collect CA/P-only chain identifiers
for (int i = 0, nrows = arr_type->get_nrows(); i < nrows; ++i) {

View File

@@ -0,0 +1,274 @@
#include "Test.h"
#include "pymol/zstring_view.h"
#include <map>
#include <set>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
using namespace pymol::test;
TEST_CASE("special member functions", "[zstring_view]")
{
std::string s1 = "foobar";
std::string s2 = s1; // same value, different data() pointer
std::string s3 = "other value";
// view from std::string
pymol::zstring_view v1(s1);
REQUIRE(v1 == s1);
REQUIRE(s1 == v1); // swap lhs rhs
REQUIRE(v1 == s2);
REQUIRE(v1 != s3);
REQUIRE(v1 == "foobar");
REQUIRE(v1 != "abc");
// view from view
pymol::zstring_view v2(v1);
REQUIRE(v2 == v1);
REQUIRE(v2 == s1);
REQUIRE(v2 == s2);
REQUIRE(v2 != s3);
// view assigment
v1 = v2;
REQUIRE(v1 == v2);
REQUIRE(v1 == "foobar");
// string assignment
v1 = s3;
REQUIRE(v1 != v2);
REQUIRE(v1 != s1);
REQUIRE(v1 != s2);
REQUIRE(v1 == s3);
// static string constant assingment
v1 = "constant";
REQUIRE(v1 == "constant");
REQUIRE(v1 == std::string("constant"));
// swap
std::swap(v1, v2);
REQUIRE(v1 == "foobar");
REQUIRE(v2 == "constant");
// view to string
auto s4 = std::string(v1.c_str());
REQUIRE(s4 == "foobar");
}
TEST_CASE("nullsafe", "[zstring_view]")
{
std::string s3 = "other value";
const char* c1 = nullptr;
auto nullsafe1 = pymol::null_safe_zstring_view(c1);
auto nullsafe2 = pymol::null_safe_zstring_view(nullsafe1);
auto nullsafe3 = pymol::null_safe_zstring_view(s3);
REQUIRE(nullsafe1.c_str() != nullptr);
REQUIRE(nullsafe1.c_str() == std::string(""));
REQUIRE(nullsafe1 == "");
REQUIRE("" == nullsafe1); // swap lhs rhs
REQUIRE(nullsafe1 == std::string(""));
REQUIRE(nullsafe1 == nullsafe2);
REQUIRE(nullsafe3 == s3);
REQUIRE(nullsafe3 == std::string(s3).c_str());
REQUIRE(nullsafe3 != nullsafe1);
// assign view from null-safe
pymol::zstring_view v1 = nullsafe1;
v1 = nullsafe1;
// construct view from null-safe
auto v4 = pymol::zstring_view(nullsafe3);
REQUIRE(v4 == s3);
}
TEST_CASE("substr", "[zstring_view]")
{
pymol::zstring_view v1 = "foobar";
pymol::zstring_view v2 = v1;
v1.remove_prefix(3);
REQUIRE(v1 == "bar");
REQUIRE(v2.substr(3) == "bar");
REQUIRE(v2 == "foobar");
}
TEST_CASE("size", "[zstring_view]")
{
auto v1 = pymol::zstring_view("foo");
REQUIRE(v1.size() == 3);
REQUIRE(!v1.empty());
v1 = "";
REQUIRE(v1.size() == 0);
REQUIRE(v1.empty());
}
TEST_CASE("sets", "[zstring_view]")
{
std::set<pymol::zstring_view> set1;
std::unordered_set<pymol::zstring_view> set2;
pymol::zstring_view v1 =
"some very long string which can't be small string optimized";
std::string s1 = v1.c_str(); // copy
pymol::null_safe_zstring_view nullsafe1 = s1;
REQUIRE(v1 == s1);
REQUIRE(v1 == nullsafe1);
REQUIRE(s1 == nullsafe1);
set1.insert(v1);
set1.insert(s1);
set1.insert(nullsafe1);
set2.insert(v1);
set2.insert(s1);
set2.insert(nullsafe1);
set1.insert("foo");
set2.insert("foo");
std::string s2("foo");
set1.insert(s2);
set2.insert(s2);
REQUIRE(set1.size() == 2);
REQUIRE(set2.size() == 2);
REQUIRE(set1.count("foo") == 1);
REQUIRE(set2.count("foo") == 1);
REQUIRE(set1.count("bar") == 0);
REQUIRE(set2.count("bar") == 0);
REQUIRE(set1.count(s1) == 1);
REQUIRE(set2.count(s1) == 1);
}
TEST_CASE("maps", "[zstring_view]")
{
std::map<pymol::zstring_view, int> map1;
std::unordered_map<pymol::zstring_view, int> map2;
map1["foo"] = 12;
map2["foo"] = 12;
REQUIRE(map1["foo"] == 12);
REQUIRE(map2["foo"] == 12);
REQUIRE(map1.size() == 1);
REQUIRE(map2.size() == 1);
auto s1 = std::string("foo");
REQUIRE(map1[s1] == 12);
REQUIRE(map2[s1] == 12);
map1[s1] = 34;
map2[s1] = 34;
REQUIRE(map1.size() == 1);
REQUIRE(map2.size() == 1);
REQUIRE(map1["foo"] == 34);
REQUIRE(map2["foo"] == 34);
auto s2 = std::string("bar");
REQUIRE(map1[s2] == 0);
REQUIRE(map2[s2] == 0);
map1[s2] = 56;
map2[s2] = 56;
REQUIRE(map1.size() == 2);
REQUIRE(map2.size() == 2);
REQUIRE(map1[s2] == 56);
REQUIRE(map2[s2] == 56);
}
TEST_CASE("ostream", "[zstring_view]")
{
pymol::zstring_view v1("foobar");
std::ostringstream out;
out << v1;
REQUIRE(out.str() == "foobar");
}
TEST_CASE("copy", "[zstring_view]")
{
pymol::zstring_view v1 = "foobar";
char buffer[16];
buffer[v1.copy(buffer, sizeof(buffer) - 1)] = 0;
REQUIRE(v1 == buffer);
buffer[v1.copy(buffer, 3)] = 0;
REQUIRE(std::string(v1.c_str(), 3) == buffer);
buffer[v1.copy(buffer, sizeof(buffer) - 1, 3)] = 0;
REQUIRE(std::string(v1.c_str() + 3) == buffer);
}
TEST_CASE("starts or ends with", "[zstring_view]")
{
pymol::zstring_view v1 = "foobar";
REQUIRE(v1.starts_with("foo"));
REQUIRE(!v1.starts_with("bar"));
REQUIRE(!v1.ends_with("foo"));
REQUIRE(v1.ends_with("bar"));
}
TEST_CASE("find", "[zstring_view]")
{
pymol::zstring_view v1 = "foobar";
REQUIRE(v1.find('a') == 4);
REQUIRE(v1.find("ob") == 2);
REQUIRE(v1.find("obo") == pymol::zstring_view::npos);
REQUIRE(v1.find_first_of('b') == 3);
REQUIRE(v1.find_first_of("abc") == 3);
REQUIRE(v1.find_first_not_of("abc") == 0);
REQUIRE(v1.find_first_not_of("abfor") == pymol::zstring_view::npos);
}
TEST_CASE("explicit bool cast", "[zstring_view]")
{
// default constructed is false
pymol::zstring_view v1;
REQUIRE(!v1);
// nullsafe always true
pymol::null_safe_zstring_view v2;
REQUIRE(v2);
// empty string is true
v1 = "";
REQUIRE(v1);
v2 = "";
REQUIRE(v2);
// null assignment is legal
v1 = nullptr;
REQUIRE(!v1);
v2 = nullptr;
REQUIRE(v2);
v1 = "foo";
REQUIRE(v1);
v2 = "foo";
REQUIRE(v2);
}
TEST_CASE("implicit cast", "[zstring_view]")
{
static_assert(std::is_convertible<const char*, pymol::zstring_view>::value,
"from const char*");
static_assert(!std::is_convertible<pymol::zstring_view, const char*>::value,
"to const char*");
static_assert(std::is_convertible<pymol::null_safe_zstring_view,
pymol::zstring_view>::value,
"from null_safe_zstring_view");
static_assert(std::is_convertible<pymol::zstring_view,
pymol::null_safe_zstring_view>::value,
"to null_safe_zstring_view");
static_assert(std::is_convertible<std::string, pymol::zstring_view>::value,
"from std::string");
static_assert(!std::is_convertible<pymol::zstring_view, std::string>::value,
"to std::string");
static_assert(
!std::is_convertible<bool, pymol::zstring_view>::value, "from bool");
static_assert(
!std::is_convertible<pymol::zstring_view, bool>::value, "to bool");
}

View File

@@ -237,6 +237,7 @@ create_shadertext.create_all(generated_dir)
prefix_path = get_prefix_path()
inc_dirs = [
"include",
]
pymol_src_dirs = [