From 9d19a046145c3e90db28f464f2c937ba86948f34 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 6 Oct 2018 19:34:20 +0700 Subject: [PATCH 1/5] strings include guards --- headers/enduro2d/utils/strings.hpp | 3 +++ headers/enduro2d/utils/strings.inl | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/headers/enduro2d/utils/strings.hpp b/headers/enduro2d/utils/strings.hpp index 6949f913..e8ad29ed 100644 --- a/headers/enduro2d/utils/strings.hpp +++ b/headers/enduro2d/utils/strings.hpp @@ -4,6 +4,8 @@ * Copyright (C) 2018 Matvey Cherevko ******************************************************************************/ +#ifndef E2D_INCLUDE_GUARD_4540A3A22AD942D8A6B0C77A3346E73A +#define E2D_INCLUDE_GUARD_4540A3A22AD942D8A6B0C77A3346E73A #pragma once #include "_utils.hpp" @@ -56,3 +58,4 @@ namespace e2d } #include "strings.inl" +#endif diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index 29e43f38..af9e0cdf 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -4,6 +4,8 @@ * Copyright (C) 2018 Matvey Cherevko ******************************************************************************/ +#ifndef E2D_INCLUDE_GUARD_5BC5A803A3694674845E9953209E8CBE +#define E2D_INCLUDE_GUARD_5BC5A803A3694674845E9953209E8CBE #pragma once #include "_utils.hpp" @@ -390,3 +392,5 @@ namespace e2d { namespace strings return str(buffer.data(), buffer.data() + actual_format_size); } }} + +#endif From b974ad139f27dcf4bc78de1954e0dd4361ebd628 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 4 Oct 2018 19:36:51 +0700 Subject: [PATCH 2/5] null char* strings format support --- headers/enduro2d/utils/strings.inl | 6 ++++++ untests/sources/untests_utils/strings.cpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index af9e0cdf..ba091195 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -186,6 +186,9 @@ namespace e2d { namespace strings : value_(value), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const noexcept { + if ( !value_ ) { + return 0; + } char format[6] = {0}; char* b_format = format; *b_format++ = '%'; @@ -206,6 +209,9 @@ namespace e2d { namespace strings : value_(value), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const noexcept { + if ( !value_ ) { + return 0; + } char format[6] = {0}; char* b_format = format; *b_format++ = '%'; diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp index 7a7e0c66..634397d0 100644 --- a/untests/sources/untests_utils/strings.cpp +++ b/untests/sources/untests_utils/strings.cpp @@ -344,6 +344,10 @@ TEST_CASE("strings") { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) == str("0 2 1 4 3 6 7 5 8 9")); } + { + REQUIRE(strings::rformat("%0", static_cast(nullptr)).empty()); + REQUIRE(strings::rformat("%0", static_cast(nullptr)).empty()); + } { REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(4))) == " ab"); REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(3))) == " ab"); From b2c4489592cc96a431e623fb58002435dff46484 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 6 Oct 2018 19:39:24 +0700 Subject: [PATCH 3/5] nothrow variants for strings format --- headers/enduro2d/utils/strings.hpp | 13 ++++++++++- headers/enduro2d/utils/strings.inl | 27 +++++++++++++++++++++++ untests/sources/untests_utils/strings.cpp | 12 ++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/headers/enduro2d/utils/strings.hpp b/headers/enduro2d/utils/strings.hpp index e8ad29ed..877cf7ed 100644 --- a/headers/enduro2d/utils/strings.hpp +++ b/headers/enduro2d/utils/strings.hpp @@ -51,7 +51,18 @@ namespace e2d str_view fmt, Args&&... args); template < typename... Args > - str rformat(str_view fmt, Args&&... args); + bool format_nothrow( + char* dst, std::size_t dst_size, std::size_t* length, + str_view fmt, Args&&... args) noexcept; + + template < typename... Args > + str rformat( + str_view fmt, Args&&... args); + + template < typename... Args > + bool rformat_nothrow( + str& dst, + str_view fmt, Args&&... args) noexcept; bool wildcard_match(str_view string, str_view pattern); } diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index ba091195..3f942315 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -385,6 +385,23 @@ namespace e2d { namespace strings std::make_tuple(impl::wrap_arg(std::forward(args))...)); } + template < typename... Args > + bool format_nothrow( + char* dst, std::size_t dst_size, std::size_t* length, + str_view fmt, Args&&... args) noexcept + { + try { + std::size_t result = format( + dst, dst_size, fmt, std::forward(args)...); + if ( length ) { + *length = result; + } + return true; + } catch (...) { + return false; + } + } + template < typename... Args > str rformat(str_view fmt, Args&&... args) { auto targs = std::make_tuple( @@ -397,6 +414,16 @@ namespace e2d { namespace strings E2D_ASSERT(expected_format_size == actual_format_size); return str(buffer.data(), buffer.data() + actual_format_size); } + + template < typename... Args > + bool rformat_nothrow(str& dst, str_view fmt, Args&&... args) noexcept { + try { + dst = rformat(fmt, std::forward(args)...); + return true; + } catch (...) { + return false; + } + } }} #endif diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp index 634397d0..3e1cdb3d 100644 --- a/untests/sources/untests_utils/strings.cpp +++ b/untests/sources/untests_utils/strings.cpp @@ -250,6 +250,18 @@ TEST_CASE("strings") { REQUIRE_THROWS_AS(strings::rformat("hell%y%"), strings::bad_format); REQUIRE_THROWS_AS(strings::rformat("%z%hell"), strings::bad_format); } + { + str s; + REQUIRE(strings::rformat_nothrow(s, "%0", "hello")); + REQUIRE(s == "hello"); + REQUIRE_FALSE(strings::rformat_nothrow(s, "%")); + char buf[5] = {0}; + std::size_t length = 0; + REQUIRE(strings::format_nothrow(buf, E2D_COUNTOF(buf), &length, "%0", "hell")); + REQUIRE(length == 4); + REQUIRE(str(buf) == str("hell")); + REQUIRE_FALSE(strings::format_nothrow(buf, E2D_COUNTOF(buf), &length, "%0", "hello")); + } { REQUIRE(strings::rformat(str_view("%0"), 42) == "42"); REQUIRE(strings::rformat(str_view("%0%1",2), 42) == "42"); From 19e5c0b6d53daf2937f20361a2934d30984ae4e1 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 8 Oct 2018 23:39:11 +0700 Subject: [PATCH 4/5] std::hash for str_view --- headers/enduro2d/base/stdex.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/headers/enduro2d/base/stdex.hpp b/headers/enduro2d/base/stdex.hpp index a984f712..80a2b15c 100644 --- a/headers/enduro2d/base/stdex.hpp +++ b/headers/enduro2d/base/stdex.hpp @@ -524,3 +524,21 @@ namespace e2d { namespace stdex return !(l == r); } }} + +namespace std +{ + template < typename Char, typename Traits > + struct hash> + : std::unary_function, std::size_t> + { + // Inspired by: + // http://www.cse.yorku.ca/~oz/hash.html + std::size_t operator()(e2d::stdex::basic_string_view sv) const noexcept { + std::size_t hash = 0; + for ( Char c : sv ) { + hash = c + (hash << 6u) + (hash << 16u) - hash; + } + return hash; + } + }; +} From b1d45485191fd4dd2e98bde5e11a3ac4fa50fbfd Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 10 Oct 2018 04:40:36 +0700 Subject: [PATCH 5/5] basic_string_hash class --- headers/enduro2d/utils/_utils.hpp | 13 +- headers/enduro2d/utils/strings.hpp | 54 +++++++ headers/enduro2d/utils/strings.inl | 170 ++++++++++++++++++++++ sources/enduro2d/utils/strings.cpp | 20 +++ untests/sources/untests_utils/strings.cpp | 39 +++++ 5 files changed, 294 insertions(+), 2 deletions(-) diff --git a/headers/enduro2d/utils/_utils.hpp b/headers/enduro2d/utils/_utils.hpp index e781757f..1e858619 100644 --- a/headers/enduro2d/utils/_utils.hpp +++ b/headers/enduro2d/utils/_utils.hpp @@ -24,6 +24,9 @@ namespace e2d template < typename T > class module; + + template < typename Char > + class basic_string_hash; } namespace e2d @@ -38,6 +41,11 @@ namespace e2d using str16_view = basic_string_view; using str32_view = basic_string_view; + using str_hash = basic_string_hash; + using wstr_hash = basic_string_hash; + using str16_hash = basic_string_hash; + using str32_hash = basic_string_hash; + struct seconds_tag {}; struct milliseconds_tag {}; struct microseconds_tag {}; @@ -53,10 +61,11 @@ namespace e2d namespace e2d { class noncopyable { + public: + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; protected: noncopyable() = default; ~noncopyable() = default; - noncopyable(const noncopyable&) = delete; - noncopyable& operator=(const noncopyable&) = delete; }; } diff --git a/headers/enduro2d/utils/strings.hpp b/headers/enduro2d/utils/strings.hpp index 877cf7ed..d9bed535 100644 --- a/headers/enduro2d/utils/strings.hpp +++ b/headers/enduro2d/utils/strings.hpp @@ -10,6 +10,55 @@ #include "_utils.hpp" +namespace e2d +{ + template < typename Char > + class basic_string_hash final { + public: + static std::size_t empty_hash() noexcept; + public: + basic_string_hash() noexcept; + ~basic_string_hash() noexcept; + + basic_string_hash(basic_string_hash&& other) noexcept; + basic_string_hash& operator=(basic_string_hash&& other) noexcept; + + basic_string_hash(const basic_string_hash& other) noexcept; + basic_string_hash& operator=(const basic_string_hash& other) noexcept; + + explicit basic_string_hash(basic_string_view str) noexcept; + + basic_string_hash& assign(basic_string_hash&& other) noexcept; + basic_string_hash& assign(const basic_string_hash& other) noexcept; + basic_string_hash& assign(basic_string_view str) noexcept; + + void swap(basic_string_hash& other) noexcept; + void clear() noexcept; + bool empty() const noexcept; + + std::size_t hash() const noexcept; + private: + static std::size_t calculate_hash( + basic_string_view str) noexcept; + static void debug_check_collisions( + std::size_t hash, basic_string_view str) noexcept; + private: + std::size_t hash_ = empty_hash(); + }; + + template < typename Char > + void swap(basic_string_hash& l, basic_string_hash& r) noexcept; + + template < typename Char > + bool operator<(basic_string_hash l, basic_string_hash r) noexcept; + + template < typename Char > + bool operator==(basic_string_hash l, basic_string_hash r) noexcept; + + template < typename Char > + bool operator!=(basic_string_hash l, basic_string_hash r) noexcept; +} + namespace e2d { str make_utf8(str_view src); @@ -32,6 +81,11 @@ namespace e2d str32 make_utf32(str16_view src); str32 make_utf32(str32_view src); + str_hash make_hash(str_view src); + wstr_hash make_hash(wstr_view src); + str16_hash make_hash(str16_view src); + str32_hash make_hash(str32_view src); + namespace strings { class format_error; diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index 3f942315..42410251 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -11,6 +11,176 @@ #include "_utils.hpp" #include "strings.hpp" +namespace e2d +{ + template < typename Char > + std::size_t basic_string_hash::empty_hash() noexcept { + static std::size_t hash = calculate_hash(basic_string_view()); + return hash; + } + + template < typename Char > + basic_string_hash::basic_string_hash() noexcept = default; + + template < typename Char > + basic_string_hash::~basic_string_hash() noexcept = default; + + template < typename Char > + basic_string_hash::basic_string_hash( + basic_string_hash&& other) noexcept + { + assign(std::move(other)); + } + + template < typename Char > + basic_string_hash& basic_string_hash::operator=( + basic_string_hash&& other) noexcept + { + return assign(std::move(other)); + } + + template < typename Char > + basic_string_hash::basic_string_hash( + const basic_string_hash& other) noexcept + { + assign(other); + } + + template < typename Char > + basic_string_hash& basic_string_hash::operator=( + const basic_string_hash& other) noexcept + { + return assign(other); + } + + template < typename Char > + basic_string_hash::basic_string_hash( + basic_string_view str) noexcept + { + assign(str); + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + basic_string_hash&& other) noexcept + { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + const basic_string_hash& other) noexcept + { + if ( this != &other ) { + hash_ = other.hash_; + } + return *this; + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + basic_string_view str) noexcept + { + hash_ = calculate_hash(str); + return *this; + } + + template < typename Char > + void basic_string_hash::swap(basic_string_hash& other) noexcept { + using std::swap; + swap(hash_, other.hash_); + } + + template < typename Char > + void basic_string_hash::clear() noexcept { + hash_ = empty_hash(); + } + + template < typename Char > + bool basic_string_hash::empty() const noexcept { + return hash_ == empty_hash(); + } + + template < typename Char > + std::size_t basic_string_hash::hash() const noexcept { + return hash_; + } + + template < typename Char > + std::size_t basic_string_hash::calculate_hash( + basic_string_view str) noexcept + { + std::size_t hash = std::hash>()(str); + debug_check_collisions(hash, str); + return hash; + } + + template < typename Char > + void basic_string_hash::debug_check_collisions( + std::size_t hash, basic_string_view str) noexcept + { + #if defined(E2D_BUILD_MODE) && E2D_BUILD_MODE == E2D_BUILD_MODE_DEBUG + try { + static std::mutex mutex; + static hash_map> table; + std::lock_guard guard(mutex); + const auto iter = table.find(hash); + if ( iter != table.end() ) { + E2D_ASSERT_MSG( + iter->second == str, + "basic_string_hash: hash collision detected"); + } else { + table.insert(std::make_pair(hash, str)); + } + } catch (...) { + E2D_ASSERT_MSG(false, "basic_string_hash: unexpected exception"); + throw; + } + #else + E2D_UNUSED(hash, str); + #endif + } +} + +namespace e2d +{ + template < typename Char > + void swap(basic_string_hash& l, basic_string_hash& r) noexcept { + l.swap(r); + } + + template < typename Char > + bool operator<(basic_string_hash l, basic_string_hash r) noexcept { + return l.hash() < r.hash(); + } + + template < typename Char > + bool operator==(basic_string_hash l, basic_string_hash r) noexcept { + return l.hash() == r.hash(); + } + + template < typename Char > + bool operator!=(basic_string_hash l, basic_string_hash r) noexcept { + return !(l == r); + } +} + +namespace std +{ + template < typename Char > + struct hash> + : std::unary_function, std::size_t> + { + std::size_t operator()(e2d::basic_string_hash hs) const noexcept { + return hs.hash(); + } + }; +} + namespace e2d { namespace strings { // diff --git a/sources/enduro2d/utils/strings.cpp b/sources/enduro2d/utils/strings.cpp index 794e7689..412347e3 100644 --- a/sources/enduro2d/utils/strings.cpp +++ b/sources/enduro2d/utils/strings.cpp @@ -238,6 +238,26 @@ namespace e2d ? str32() : str32(src.cbegin(), src.cend()); } + + // + // make_hash + // + + str_hash make_hash(str_view src) { + return str_hash(src); + } + + wstr_hash make_hash(wstr_view src) { + return wstr_hash(src); + } + + str16_hash make_hash(str16_view src) { + return str16_hash(src); + } + + str32_hash make_hash(str32_view src) { + return str32_hash(src); + } } namespace e2d { namespace strings diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp index 3e1cdb3d..03030de7 100644 --- a/untests/sources/untests_utils/strings.cpp +++ b/untests/sources/untests_utils/strings.cpp @@ -18,6 +18,45 @@ namespace } TEST_CASE("strings") { + { + REQUIRE(str_hash().empty()); + REQUIRE(str_hash("").empty()); + REQUIRE_FALSE(str_hash("1").empty()); + + REQUIRE(str_hash().hash() == str_hash().hash()); + REQUIRE(str_hash().hash() == str_hash({null_utf8,0}).hash()); + REQUIRE(str_hash().hash() == wstr_hash({null_wide,0}).hash()); + REQUIRE(str_hash().hash() == str16_hash({null_utf16,0}).hash()); + REQUIRE(str_hash().hash() == str32_hash({null_utf32,0}).hash()); + REQUIRE(str_hash("hello").hash() == str_hash("hello").hash()); + REQUIRE(str_hash("world").hash() == str_hash("world").hash()); + REQUIRE(str_hash("hello").hash() != str_hash("world").hash()); + + REQUIRE(str_hash() == str_hash()); + REQUIRE(str_hash() == make_hash({null_utf8,0})); + REQUIRE(wstr_hash() == make_hash({null_wide,0})); + REQUIRE(str16_hash() == make_hash({null_utf16,0})); + REQUIRE(str32_hash() == make_hash({null_utf32,0})); + REQUIRE(str_hash("hello") == make_hash("hello")); + REQUIRE(str_hash("world") == make_hash("world")); + REQUIRE(str_hash("hello") != make_hash("world")); + + REQUIRE(str_hash("hello").assign("world") == make_hash("world")); + REQUIRE(str_hash("hello").assign(make_hash("world")) == make_hash("world")); + { + str_hash s("hello"); + s.clear(); + REQUIRE(s.empty()); + REQUIRE(s == str_hash()); + } + { + str_hash s1("hello"); + str_hash s2("world"); + s1.swap(s2); + REQUIRE(s2 == str_hash("hello")); + REQUIRE(s1 == make_hash("world")); + } + } { REQUIRE(make_utf8("hello") == "hello"); REQUIRE(make_utf8(L"hello") == "hello");