mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-03 19:54:24 +08:00
pymol::zstring_view
This commit is contained in:
260
include/pymol/zstring_view.h
Normal file
260
include/pymol/zstring_view.h
Normal 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
|
||||
@@ -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++)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
274
layerCTest/Test_zstring_view.cpp
Normal file
274
layerCTest/Test_zstring_view.cpp
Normal 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");
|
||||
}
|
||||
Reference in New Issue
Block a user