diff options
author | B. Stack <bgstack15@gmail.com> | 2022-05-22 21:09:26 +0000 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2022-05-22 21:09:26 +0000 |
commit | 54c2e44d7b37b2c3efc449e054eef21fa414dfde (patch) | |
tree | 3894ba7e10c78750c195381a861da5e8166a6bfd /zen | |
parent | Merge branch 'b11.20' into 'master' (diff) | |
parent | add upstream 11.21 (diff) | |
download | FreeFileSync-54c2e44d7b37b2c3efc449e054eef21fa414dfde.tar.gz FreeFileSync-54c2e44d7b37b2c3efc449e054eef21fa414dfde.tar.bz2 FreeFileSync-54c2e44d7b37b2c3efc449e054eef21fa414dfde.zip |
Merge branch 'b11.21' into 'master'11.21
add upstream 11.21
See merge request opensource-tracking/FreeFileSync!44
Diffstat (limited to 'zen')
42 files changed, 627 insertions, 547 deletions
diff --git a/zen/base64.h b/zen/base64.h index f03a1433..48cf2230 100644 --- a/zen/base64.h +++ b/zen/base64.h @@ -7,6 +7,7 @@ #ifndef BASE64_H_08473021856321840873021487213453214 #define BASE64_H_08473021856321840873021487213453214 +#include <cassert> #include <iterator> #include "type_traits.h" @@ -55,7 +56,7 @@ constexpr signed char DECODING_MIME[] = -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; -const unsigned char INDEX_PAD = 64; //"=" +const unsigned char INDEX_PAD = 64; //index of "=" } @@ -64,8 +65,8 @@ OutputIterator encodeBase64(InputIterator first, InputIterator last, OutputItera { using namespace impl; static_assert(sizeof(typename std::iterator_traits<InputIterator>::value_type) == 1); - static_assert(arraySize(ENCODING_MIME) == 64 + 1 + 1); - static_assert(arrayAccumulate<int>(ENCODING_MIME) + INDEX_PAD == 5602); + static_assert(std::size(ENCODING_MIME) == 64 + 1 + 1); + static_assert(arrayHash(ENCODING_MIME) == 1616767125); while (first != last) { @@ -101,8 +102,8 @@ OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputItera { using namespace impl; static_assert(sizeof(typename std::iterator_traits<InputIterator>::value_type) == 1); - static_assert(arraySize(DECODING_MIME) == 128); - static_assert(arrayAccumulate<int>(DECODING_MIME) + INDEX_PAD == 2081); + static_assert(std::size(DECODING_MIME) == 128); + static_assert(arrayHash(DECODING_MIME)== 1169145114); const unsigned char INDEX_END = INDEX_PAD + 1; @@ -114,7 +115,7 @@ OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputItera return INDEX_END; const unsigned char ch = static_cast<unsigned char>(*first++); - if (ch < arraySize(DECODING_MIME)) //we're in lower ASCII table half + if (ch < std::size(DECODING_MIME)) //we're in lower ASCII table half { const int index = DECODING_MIME[ch]; if (0 <= index && index <= static_cast<int>(INDEX_PAD)) //skip all unknown characters (including carriage return, line-break, tab) diff --git a/zen/basic_math.h b/zen/basic_math.h index 944a0f53..c8a06b78 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -8,7 +8,6 @@ #define BASIC_MATH_H_3472639843265675 #include <cassert> -#include <algorithm> #include <cmath> #include <numbers> #include "type_traits.h" @@ -152,10 +151,10 @@ template <class N, class D> inline auto intDivRound(N num, D den) { using namespace zen; - static_assert(IsIntegerV<N>&& IsIntegerV<D>); - static_assert(IsSignedIntV<N> == IsSignedIntV<D>); //until further + static_assert(isInteger<N>&& isInteger<D>); + static_assert(isSignedInt<N> == isSignedInt<D>); //until further assert(den != 0); - if constexpr (IsSignedIntV<N>) + if constexpr (isSignedInt<N>) { if ((num < 0) != (den < 0)) return (num - den / 2) / den; @@ -168,10 +167,10 @@ template <class N, class D> inline auto intDivCeil(N num, D den) { using namespace zen; - static_assert(IsIntegerV<N>&& IsIntegerV<D>); - static_assert(IsSignedIntV<N> == IsSignedIntV<D>); //until further + static_assert(isInteger<N>&& isInteger<D>); + static_assert(isSignedInt<N> == isSignedInt<D>); //until further assert(den != 0); - if constexpr (IsSignedIntV<N>) + if constexpr (isSignedInt<N>) { if ((num < 0) != (den < 0)) return num / den; @@ -187,10 +186,10 @@ template <class N, class D> inline auto intDivFloor(N num, D den) { using namespace zen; - static_assert(IsIntegerV<N>&& IsIntegerV<D>); - static_assert(IsSignedIntV<N> == IsSignedIntV<D>); //until further + static_assert(isInteger<N>&& isInteger<D>); + static_assert(isSignedInt<N> == isSignedInt<D>); //until further assert(den != 0); - if constexpr (IsSignedIntV<N>) + if constexpr (isSignedInt<N>) { if ((num < 0) != (den < 0)) { diff --git a/zen/build_info.h b/zen/build_info.h index 5a1d1635..b06c1302 100644 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -8,25 +8,25 @@ #define BUILD_INFO_H_5928539285603428657 -#define ZEN_ARCH_32BIT 32 -#define ZEN_ARCH_64BIT 64 - #ifdef __LP64__ - #define ZEN_BUILD_ARCH ZEN_ARCH_64BIT - #else - #define ZEN_BUILD_ARCH ZEN_ARCH_32BIT - #endif +namespace zen +{ +enum class BuildArch +{ + bit32, + bit64, -static_assert(ZEN_BUILD_ARCH == sizeof(void*) * 8); +#ifdef __LP64__ + program = bit64 +#else + program = bit32 +#endif +}; +static_assert((BuildArch::program == BuildArch::bit32 ? 32 : 64) == sizeof(void*) * 8); -namespace zen -{ - #if ZEN_BUILD_ARCH == ZEN_ARCH_32BIT - const char cpuArchName[] = "i686"; - #else - const char cpuArchName[] = "x86-64"; - #endif + +constexpr const char* cpuArchName = BuildArch::program == BuildArch::bit32 ? "i686": "x86-64"; } @@ -52,7 +52,9 @@ uint16_t getCrc16(ByteIterator first, ByteIterator last) //http://www.sunshine2k 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 }; - static_assert(arraySize(crcTable) == 256 && arrayAccumulate<uint32_t>(crcTable) == 8380544); + static_assert(std::size(crcTable) == 256); + static_assert(arrayHash(crcTable) == 728085957); + crc = (crc >> 8) ^ crcTable[(crc ^ b) & 0xFF]; }); return crc; @@ -96,7 +98,8 @@ uint32_t getCrc32(ByteIterator first, ByteIterator last) //https://en.wikipedia. 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; - static_assert(arraySize(crcTable) == 256 && arrayAccumulate<uint64_t>(crcTable) == 549755813760); + static_assert(std::size(crcTable) == 256); + static_assert(arrayHash(crcTable) == 2988069445); crc = (crc >> 8) ^ crcTable[(crc ^ b) & 0xFF]; }); diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 191ffd64..c48928a3 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -24,7 +24,7 @@ using namespace zen; struct DirWatcher::Impl { int notifDescr = 0; - std::map<int, Zstring> watchedPaths; //watch descriptor and (sub-)directory paths -> owned by "notifDescr" + std::unordered_map<int, Zstring> watchedPaths; //watch descriptor and (sub-)directory paths -> owned by "notifDescr" }; @@ -133,7 +133,7 @@ std::vector<DirWatcher::Change> DirWatcher::fetchChanges(const std::function<voi { //Note: evt.len is NOT the size of the evt.name c-string, but the array size including all padding 0 characters! //It may be even 0 in which case evt.name must not be used! - const Zstring itemPath = appendSeparator(it->second) + evt.name; + const Zstring itemPath = appendPath(it->second, evt.name); if ((evt.mask & IN_CREATE) || (evt.mask & IN_MOVED_TO)) diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index bf71fda9..a4a061e8 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -8,7 +8,7 @@ #define DIR_WATCHER_348577025748023458 #include <vector> -#include <memory> +//#include <memory> #include <chrono> #include <functional> #include "file_error.h" diff --git a/zen/error_log.h b/zen/error_log.h index a24dfe5a..357232f3 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -11,7 +11,6 @@ #include <vector> #include "time.h" #include "i18n.h" -#include "utf.h" #include "zstring.h" @@ -37,7 +36,7 @@ std::string formatMessage(const LogEntry& entry); class ErrorLog { public: - void logMsg(const std::wstring& msg, MessageType type); + void logMsg(const std::wstring& msg, MessageType type, time_t time = std::time(nullptr)); struct Stats { @@ -66,9 +65,9 @@ private: //######################## implementation ########################## inline -void ErrorLog::logMsg(const std::wstring& msg, MessageType type) +void ErrorLog::logMsg(const std::wstring& msg, MessageType type, time_t time) { - entries_.push_back({std::time(nullptr), type, utfTo<Zstringc>(msg)}); + entries_.push_back({time, type, utfTo<Zstringc>(msg)}); } @@ -119,14 +118,13 @@ std::string formatMessage(const LogEntry& entry) const size_t prefixLen = unicodeLength(msgFmt); //consider Unicode! const Zstringc msg = trimCpy(entry.message); - static_assert(std::is_same_v<decltype(msg), const Zstringc>, "don't worry about copying as long as we're using a ref-counted string!"); + static_assert(std::is_same_v<decltype(msg), const Zstringc>, "no worries about copying as long as we're using a ref-counted string!"); for (auto it = msg.begin(); it != msg.end(); ) if (*it == '\n') { - msgFmt += '\n'; + msgFmt += *it++; msgFmt.append(prefixLen, ' '); - ++it; //skip duplicate newlines for (; it != msg.end() && *it == '\n'; ++it) ; diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 2fbcf803..6a62f671 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -7,7 +7,6 @@ #include "file_access.h" #include <map> #include <algorithm> -#include <stdexcept> #include <chrono> #include "file_traverser.h" #include "scope_guard.h" @@ -17,7 +16,6 @@ #include "guid.h" #include <sys/vfs.h> //statfs - //#include <sys/time.h> //lutimes #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif diff --git a/zen/file_access.h b/zen/file_access.h index 691a8df9..17c47731 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -8,7 +8,7 @@ #define FILE_ACCESS_H_8017341345614857 #include <functional> -#include "zstring.h" +#include "file_path.h" #include "file_error.h" #include "serialize.h" //IoCallback #include <sys/stat.h> diff --git a/zen/file_error.h b/zen/file_error.h index b9ce9419..168ea806 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -7,7 +7,6 @@ #ifndef FILE_ERROR_H_839567308565656789 #define FILE_ERROR_H_839567308565656789 -#include "zstring.h" #include "sys_error.h" //we'll need this later anyway! diff --git a/zen/file_io.h b/zen/file_io.h index 3d1dfee7..f1e4200d 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -7,9 +7,8 @@ #ifndef FILE_IO_H_89578342758342572345 #define FILE_IO_H_89578342758342572345 -#include "file_error.h" #include "file_access.h" -#include "serialize.h" +//#include "serialize.h" #include "crc.h" #include "guid.h" diff --git a/zen/file_path.cpp b/zen/file_path.cpp index d846e804..926b5c89 100644 --- a/zen/file_path.cpp +++ b/zen/file_path.cpp @@ -13,18 +13,18 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath) { auto doParse = [&](int sepCountVolumeRoot, bool rootWithSep) -> std::optional<PathComponents> { - const Zstring itemPathFmt = appendSeparator(itemPath); //simplify analysis of root without separator, e.g. \\server-name\share + const Zstring itemPathPf = appendSeparator(itemPath); //simplify analysis of root without separator, e.g. \\server-name\share int sepCount = 0; - for (auto it = itemPathFmt.begin(); it != itemPathFmt.end(); ++it) + for (auto it = itemPathPf.begin(); it != itemPathPf.end(); ++it) if (*it == FILE_NAME_SEPARATOR) if (++sepCount == sepCountVolumeRoot) { - Zstring rootPath(itemPathFmt.begin(), rootWithSep ? it + 1 : it); + Zstring rootPath(itemPathPf.begin(), rootWithSep ? it + 1 : it); - Zstring relPath(it + 1, itemPathFmt.end()); + Zstring relPath(it + 1, itemPathPf.end()); trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; }); - return PathComponents({rootPath, relPath}); + return PathComponents{std::move(rootPath), std::move(relPath)}; } return {}; }; @@ -62,18 +62,103 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath) std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) { - if (const std::optional<PathComponents> comp = parsePathComponents(itemPath)) + if (const std::optional<PathComponents> pc = parsePathComponents(itemPath)) { - if (comp->relPath.empty()) + if (pc->relPath.empty()) return std::nullopt; - const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); - if (parentRelPath.empty()) - return comp->rootPath; - return appendSeparator(comp->rootPath) + parentRelPath; + return appendPath(pc->rootPath, beforeLast(pc->relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); } assert(false); return std::nullopt; } +Zstring zen::appendSeparator(Zstring path) //support rvalue references! +{ + if (!endsWith(path, FILE_NAME_SEPARATOR)) + path += FILE_NAME_SEPARATOR; + return path; //returning a by-value parameter => RVO if possible, r-value otherwise! +} + + +bool zen::isValidRelPath(const Zstring& relPath) +{ + //relPath is expected to use FILE_NAME_SEPARATOR! + if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) if (contains(relPath, Zstr('/' ))) return false; + if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) if (contains(relPath, Zstr('\\'))) return false; + + const Zchar doubleSep[] = {FILE_NAME_SEPARATOR, FILE_NAME_SEPARATOR, 0}; + return !startsWith(relPath, FILE_NAME_SEPARATOR)&& !endsWith(relPath, FILE_NAME_SEPARATOR)&& + !contains(relPath, doubleSep); +} + + +Zstring zen::appendPath(const Zstring& basePath, const Zstring& relPath) +{ + assert(isValidRelPath(relPath)); + if (relPath.empty()) + return basePath; //with or without path separator, e.g. C:\ or C:\folder + + if (basePath.empty()) //basePath might be a relative path, too! + return relPath; + + if (endsWith(basePath, FILE_NAME_SEPARATOR)) + return basePath + relPath; + + Zstring output = basePath; + output.reserve(basePath.size() + 1 + relPath.size()); //append all three strings using a single memory allocation + return std::move(output) + FILE_NAME_SEPARATOR + relPath; // +} + + +Zstring zen::getFileExtension(const Zstring& filePath) +{ + //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + //return afterLast(fileName, Zstr('.'), IfNotFoundReturn::none); + + auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); + if (it == filePath.end()) + it = filePath.begin(); + else + ++it; + + auto it2 = zen::findLast(it, filePath.end(), Zstr('.')); + if (it2 != filePath.end()) + ++it2; + + return Zstring(it2, filePath.end()); +} + + +/* https://docs.microsoft.com/de-de/windows/desktop/Intl/handling-sorting-in-your-applications + + Perf test: compare strings 10 mio times; 64 bit build + ----------------------------------------------------- + string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-" + string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf" + + Windows (UTF16 wchar_t) + 4 ns | wcscmp + 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase + 314 ns | LCMapString + wmemcmp + + OS X (UTF8 char) + 6 ns | strcmp + 98 ns | strcasecmp + 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs); + 856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive) + 1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive) + ________________________ + time per call | function */ + +std::weak_ordering zen::compareNativePath(const Zstring& lhs, const Zstring& rhs) +{ + assert(lhs.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! + assert(rhs.find(Zchar('\0')) == Zstring::npos); // + + return lhs <=> rhs; + +} + + diff --git a/zen/file_path.h b/zen/file_path.h index e328fa8e..4a85514b 100644 --- a/zen/file_path.h +++ b/zen/file_path.h @@ -12,6 +12,8 @@ namespace zen { + const Zchar FILE_NAME_SEPARATOR = '/'; + struct PathComponents { Zstring rootPath; //itemPath = rootPath + (FILE_NAME_SEPARATOR?) + relPath @@ -21,6 +23,26 @@ std::optional<PathComponents> parsePathComponents(const Zstring& itemPath); //no std::optional<Zstring> getParentFolderPath(const Zstring& itemPath); +Zstring appendSeparator(Zstring path); //support rvalue references! + +bool isValidRelPath(const Zstring& relPath); + +Zstring appendPath(const Zstring& basePath, const Zstring& relPath); + +Zstring getFileExtension(const Zstring& filePath); + +//------------------------------------------------------------------------------------------ +/* Compare *local* file paths: + Windows: igore case (but distinguish Unicode normalization forms!) + Linux: byte-wise comparison + macOS: ignore case + Unicode normalization forms */ +std::weak_ordering compareNativePath(const Zstring& lhs, const Zstring& rhs); + +inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == std::weak_ordering::equivalent; } + +struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return std::is_lt(compareNativePath(lhs, rhs)); } }; +//------------------------------------------------------------------------------------------ + } diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 515580ae..ff588562 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -51,7 +51,7 @@ void zen::traverseFolder(const Zstring& dirPath, if (itemName.empty()) //checks result of normalizeUtfForPosix, too! throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("readdir", L"", L"Folder contains an item without name.")); - const Zstring& itemPath = appendSeparator(dirPath) + itemName; + const Zstring& itemPath = appendPath(dirPath, itemName); struct stat statData = {}; try diff --git a/zen/file_traverser.h b/zen/file_traverser.h index e49219f9..cb7782d6 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -7,10 +7,8 @@ #ifndef FILER_TRAVERSER_H_127463214871234 #define FILER_TRAVERSER_H_127463214871234 -#include <cstdint> #include <functional> -#include "zstring.h" - +#include "file_path.h" namespace zen { diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 6803523a..13280b68 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -7,7 +7,6 @@ #include "format_unit.h" #include <ctime> #include <cstdio> -#include <stdexcept> #include "basic_math.h" #include "sys_error.h" #include "i18n.h" diff --git a/zen/format_unit.h b/zen/format_unit.h index 72a5e91b..d1ebc28c 100644 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -8,8 +8,9 @@ #define FMT_UNIT_8702184019487324 #include <string> -#include <cstdint> -#include "string_tools.h" +#include <optional> +//#include <cstdint> +//#include "string_tools.h" namespace zen diff --git a/zen/http.cpp b/zen/http.cpp index 4fe43ede..a26bb3a5 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -47,7 +47,7 @@ public: throw SysError(L"URL uses unexpected protocol."); }(); - std::map<std::string, std::string, LessAsciiNoCase> headers; + std::unordered_map<std::string, std::string, StringHashAsciiNoCase, StringEqualAsciiNoCase> headers; assert(postBuf || contentType.empty()); if (postBuf && !contentType.empty()) @@ -196,7 +196,7 @@ private: InterruptibleThread worker_; int64_t totalBytesReported_ = 0; int statusCode_ = 0; - std::map<std::string, std::string, LessAsciiNoCase> responseHeaders_; + std::unordered_map<std::string, std::string, StringHashAsciiNoCase, StringEqualAsciiNoCase> responseHeaders_; const IoCallback notifyUnbufferedIO_; //throw X }; @@ -7,9 +7,8 @@ #ifndef HTTP_H_879083425703425702 #define HTTP_H_879083425703425702 -#include <zen/zstring.h> -#include <zen/sys_error.h> -#include <zen/serialize.h> +#include "sys_error.h" +#include "serialize.h" namespace zen { @@ -7,8 +7,8 @@ #ifndef I18_N_H_3843489325044253425456 #define I18_N_H_3843489325044253425456 -#include <string> -#include <cstdint> +//#include <string> +//#include <cstdint> #include "globals.h" #include "string_tools.h" #include "format_unit.h" @@ -40,9 +40,9 @@ struct JsonValue Type type = Type::null; - std::string primVal; //for primitive types - std::map<std::string, JsonValue> objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine! - std::vector<JsonValue> arrayVal; + std::string primVal; //for primitive types + std::unordered_map<std::string, JsonValue> objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine! + std::vector<JsonValue> arrayVal; }; @@ -303,26 +303,26 @@ std::string serializeJson(const JsonValue& jval, namespace json_impl { -struct Token +enum class TokenType { - enum class Type - { - eof, - curlyOpen, - curlyClose, - squareOpen, - squareClose, - colon, - comma, - string, // - number, //primitive types - boolean, // - null, // - }; + eof, + curlyOpen, + curlyClose, + squareOpen, + squareClose, + colon, + comma, + string, // + number, //primitive types + boolean, // + null, // +}; - Token(Type t) : type(t) {} +struct Token +{ + Token(TokenType t) : type(t) {} - Type type; + TokenType type; std::string primVal; //for primitive types }; @@ -341,27 +341,27 @@ public: pos_ = std::find_if_not(pos_, stream_.end(), isJsonWhiteSpace); if (pos_ == stream_.end()) - return Token::Type::eof; + return TokenType::eof; - if (*pos_ == '{') return ++pos_, Token::Type::curlyOpen; - if (*pos_ == '}') return ++pos_, Token::Type::curlyClose; - if (*pos_ == '[') return ++pos_, Token::Type::squareOpen; - if (*pos_ == ']') return ++pos_, Token::Type::squareClose; - if (*pos_ == ':') return ++pos_, Token::Type::colon; - if (*pos_ == ',') return ++pos_, Token::Type::comma; - if (startsWith("null")) return pos_ += 4, Token(Token::Type::null); + if (*pos_ == '{') return ++pos_, TokenType::curlyOpen; + if (*pos_ == '}') return ++pos_, TokenType::curlyClose; + if (*pos_ == '[') return ++pos_, TokenType::squareOpen; + if (*pos_ == ']') return ++pos_, TokenType::squareClose; + if (*pos_ == ':') return ++pos_, TokenType::colon; + if (*pos_ == ',') return ++pos_, TokenType::comma; + if (startsWith("null")) return pos_ += 4, Token(TokenType::null); if (startsWith("true")) { pos_ += 4; - Token tk(Token::Type::boolean); + Token tk(TokenType::boolean); tk.primVal = "true"; return tk; } if (startsWith("false")) { pos_ += 5; - Token tk(Token::Type::boolean); + Token tk(TokenType::boolean); tk.primVal = "false"; return tk; } @@ -371,7 +371,7 @@ public: for (auto it = ++pos_; it != stream_.end(); ++it) if (*it == '"') { - Token tk(Token::Type::string); + Token tk(TokenType::string); tk.primVal = jsonUnescape({pos_, it}); pos_ = ++it; return tk; @@ -388,7 +388,7 @@ public: if (itNumEnd == pos_) throw JsonParsingError(posRow(), posCol()); - Token tk(Token::Type::number); + Token tk(TokenType::number); tk.primVal.assign(pos_, itNumEnd); pos_ = itNumEnd; return tk; @@ -441,7 +441,7 @@ public: JsonValue parse() //throw JsonParsingError { JsonValue jval = parseValue(); //throw JsonParsingError - expectToken(Token::Type::eof); // + expectToken(TokenType::eof); // return jval; } @@ -451,73 +451,73 @@ private: JsonValue parseValue() //throw JsonParsingError { - if (token().type == Token::Type::curlyOpen) + if (token().type == TokenType::curlyOpen) { nextToken(); //throw JsonParsingError JsonValue jval(JsonValue::Type::object); - if (token().type != Token::Type::curlyClose) + if (token().type != TokenType::curlyClose) for (;;) { - expectToken(Token::Type::string); //throw JsonParsingError + expectToken(TokenType::string); //throw JsonParsingError std::string name = token().primVal; nextToken(); //throw JsonParsingError - consumeToken(Token::Type::colon); //throw JsonParsingError + consumeToken(TokenType::colon); //throw JsonParsingError JsonValue value = parseValue(); //throw JsonParsingError jval.objectVal.emplace(std::move(name), std::move(value)); - if (token().type != Token::Type::comma) + if (token().type != TokenType::comma) break; nextToken(); //throw JsonParsingError } - consumeToken(Token::Type::curlyClose); //throw JsonParsingError + consumeToken(TokenType::curlyClose); //throw JsonParsingError return jval; } - else if (token().type == Token::Type::squareOpen) + else if (token().type == TokenType::squareOpen) { nextToken(); //throw JsonParsingError JsonValue jval(JsonValue::Type::array); - if (token().type != Token::Type::squareClose) + if (token().type != TokenType::squareClose) for (;;) { JsonValue value = parseValue(); //throw JsonParsingError jval.arrayVal.emplace_back(std::move(value)); - if (token().type != Token::Type::comma) + if (token().type != TokenType::comma) break; nextToken(); //throw JsonParsingError } - consumeToken(Token::Type::squareClose); //throw JsonParsingError + consumeToken(TokenType::squareClose); //throw JsonParsingError return jval; } - else if (token().type == Token::Type::string) + else if (token().type == TokenType::string) { JsonValue jval(token().primVal); nextToken(); //throw JsonParsingError return jval; } - else if (token().type == Token::Type::number) + else if (token().type == TokenType::number) { JsonValue jval(JsonValue::Type::number); jval.primVal = token().primVal; nextToken(); //throw JsonParsingError return jval; } - else if (token().type == Token::Type::boolean) + else if (token().type == TokenType::boolean) { JsonValue jval(JsonValue::Type::boolean); jval.primVal = token().primVal; nextToken(); //throw JsonParsingError return jval; } - else if (token().type == Token::Type::null) + else if (token().type == TokenType::null) { nextToken(); //throw JsonParsingError return JsonValue(); @@ -530,13 +530,13 @@ private: void nextToken() { tk_ = scn_.getNextToken(); } //throw JsonParsingError - void expectToken(Token::Type t) //throw JsonParsingError + void expectToken(TokenType t) //throw JsonParsingError { if (token().type != t) throw JsonParsingError(scn_.posRow(), scn_.posCol()); } - void consumeToken(Token::Type t) //throw JsonParsingError + void consumeToken(TokenType t) //throw JsonParsingError { expectToken(t); //throw JsonParsingError nextToken(); // diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index 99d7582e..6dc13d3d 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -419,15 +419,13 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: { std::vector<std::string> lines; - for (auto it = keyStream.begin();;) //=> keep local: "warning: declaration of ‘it’ shadows a previous local" + split2(keyStream, isLineBreak<char>, + [&lines](const char* blockFirst, const char* blockLast) { - auto itLineBegin = std::find_if_not(it, keyStream.end(), isLineBreak<char>); - if (itLineBegin == keyStream.end()) - break; + if (blockFirst != blockLast) //consider Windows' <CR><LF> + lines.emplace_back(blockFirst, blockLast); + }); - it = std::find_if(itLineBegin + 1, keyStream.end(), isLineBreak<char>); - lines.emplace_back(itLineBegin, it); - } //----------- parse PuTTY ppk structure ---------------------------------- auto itLine = lines.begin(); if (itLine == lines.end() || !startsWith(*itLine, "PuTTY-User-Key-File-2: ")) diff --git a/zen/open_ssl.h b/zen/open_ssl.h index d1b823de..c66ad9c0 100644 --- a/zen/open_ssl.h +++ b/zen/open_ssl.h @@ -7,8 +7,7 @@ #ifndef OPEN_SSL_H_801974580936508934568792347506 #define OPEN_SSL_H_801974580936508934568792347506 -#include <zen/zstring.h> -#include <zen/sys_error.h> +#include "sys_error.h" namespace zen diff --git a/zen/process_exec.cpp b/zen/process_exec.cpp index 0c5789d5..6b670508 100644 --- a/zen/process_exec.cpp +++ b/zen/process_exec.cpp @@ -44,8 +44,8 @@ namespace std::pair<int /*exit code*/, std::string> processExecuteImpl(const Zstring& filePath, const std::vector<Zstring>& arguments, std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut { - const Zstring tempFilePath = appendSeparator(getTempFolderPath()) + //throw FileError - Zstr("FFS-") + utfTo<Zstring>(formatAsHexString(generateGUID())); + const Zstring tempFilePath = appendPath(getTempFolderPath(), //throw FileError + Zstr("FFS-") + utfTo<Zstring>(formatAsHexString(generateGUID()))); /* can't use popen(): does NOT return the exit code on Linux (despite the documentation!), although it works correctly on macOS => use pipes instead: https://linux.die.net/man/2/waitpid bonus: no need for "2>&1" to redirect STDERR to STDOUT diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp index f0a49976..2b1a82d3 100644 --- a/zen/resolve_path.cpp +++ b/zen/resolve_path.cpp @@ -8,7 +8,6 @@ #include "time.h" #include "thread.h" #include "file_access.h" -#include "file_path.h" #include <stdlib.h> //getenv() #include <unistd.h> //getcwd() @@ -18,11 +17,11 @@ using namespace zen; namespace { -std::optional<Zstring> getEnvironmentVar(const Zstring& name) +std::optional<Zstring> getEnvironmentVar(const Zchar* name) { assert(runningOnMainThread()); //getenv() is not thread-safe! - const char* buffer = ::getenv(name.c_str()); //no extended error reporting + const char* buffer = ::getenv(name); //no extended error reporting if (!buffer) return {}; Zstring value(buffer); @@ -69,7 +68,7 @@ Zstring resolveRelativePath(const Zstring& relativePath) if (const std::optional<Zstring> homeDir = getEnvironmentVar("HOME")) { if (startsWith(pathTmp, "~/")) - pathTmp = appendSeparator(*homeDir) + afterFirst(pathTmp, '/', IfNotFoundReturn::none); + pathTmp = appendPath(*homeDir, pathTmp.c_str() + 2); else //pathTmp == "~" pathTmp = *homeDir; } @@ -81,7 +80,7 @@ Zstring resolveRelativePath(const Zstring& relativePath) if (char* dirPath = ::getcwd(nullptr, 0)) { ZEN_ON_SCOPE_EXIT(::free(dirPath)); - pathTmp = appendSeparator(dirPath) + pathTmp; + pathTmp = appendPath(dirPath, pathTmp); } } } @@ -142,7 +141,7 @@ std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c } //try to resolve as environment variables - if (std::optional<Zstring> value = getEnvironmentVar(macro)) + if (std::optional<Zstring> value = getEnvironmentVar(macro.c_str())) return *value; return {}; @@ -190,57 +189,45 @@ Zstring expandVolumeName(Zstring pathPhrase) // [volname]:\folder [volname]\ } return pathPhrase; } +} -void getFolderAliasesRecursive(const Zstring& pathPhrase, std::set<Zstring, LessNativePath>& output) +std::vector<Zstring> zen::getPathPhraseAliases(const Zstring& itemPath) { + assert(!itemPath.empty()); + std::vector<Zstring> pathAliases{makePathPhrase(itemPath)}; - //3. environment variables: C:\Users\<user> -> %UserProfile% { - std::vector<std::pair<Zstring, Zstring>> macroList; - //get list of useful variables - auto addEnvVar = [&](const Zstring& envName) - { - if (std::optional<Zstring> value = getEnvironmentVar(envName)) - macroList.emplace_back(envName, *value); - }; - addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user> - //addEnvVar("USER"); -> any benefit? - //substitute paths by symbolic names - for (const auto& [macroName, macroPath] : macroList) + //environment variables: C:\Users\<user> -> %UserProfile% + auto substByMacro = [&](const Zchar* macroName, const Zstring& macroPath) { //should use a replaceCpy() that considers "local path" case-sensitivity (if only we had one...) - const Zstring pathSubst = replaceCpyAsciiNoCase(pathPhrase, macroPath, MACRO_SEP + macroName + MACRO_SEP); - if (pathSubst != pathPhrase) - output.insert(pathSubst); - } - } + if (contains(itemPath, macroPath)) + pathAliases.push_back(makePathPhrase(replaceCpyAsciiNoCase(itemPath, macroPath, MACRO_SEP + Zstring(macroName) + MACRO_SEP))); + }; + + for (const Zchar* envName : + { + "HOME", //Linux: /home/<user> Mac: /Users/<user> + //"USER", -> any benefit? + }) + if (const std::optional<Zstring> envPath = getEnvironmentVar(envName)) + substByMacro(envName, *envPath); - //4. replace (all) macros: %UserProfile% -> C:\Users\<user> - { - const Zstring pathExp = expandMacros(pathPhrase); - if (pathExp != pathPhrase) - if (output.insert(pathExp).second) - getFolderAliasesRecursive(pathExp, output); //recurse! } -} + //removeDuplicates()? should not be needed... + + std::sort(pathAliases.begin(), pathAliases.end(), LessNaturalSort() /*even on Linux*/); + return pathAliases; } -std::vector<Zstring> zen::getFolderPathAliases(const Zstring& folderPathPhrase) +Zstring zen::makePathPhrase(const Zstring& itemPath) { - const Zstring dirPath = trimCpy(folderPathPhrase); - if (dirPath.empty()) - return {}; - - std::set<Zstring, LessNativePath> tmp; - getFolderAliasesRecursive(dirPath, tmp); - - tmp.erase(dirPath); - tmp.erase(Zstring()); - - return {tmp.begin(), tmp.end()}; + if (endsWith(itemPath, Zstr(' '))) //path phrase concept must survive trimming! + return itemPath + FILE_NAME_SEPARATOR; + return itemPath; } @@ -254,26 +241,22 @@ Zstring zen::getResolvedFilePath(const Zstring& pathPhrase) //noexcept //remove leading/trailing whitespace before allowing misinterpretation in applyLongPathPrefix() trim(path); //attention: don't remove all whitespace from right, e.g. 0xa0 may be used as part of a folder name - - path = expandVolumeName(path); //may block for slow USB sticks and idle HDDs! - - /* need to resolve relative paths: - WINDOWS: - - \\?\-prefix requires absolute names - - Volume Shadow Copy: volume name needs to be part of each file path - - file icon buffer (at least for extensions that are actually read from disk, like "exe") - WINDOWS/LINUX: - - detection of dependent directories, e.g. "\" and "C:\test" */ - path = resolveRelativePath(path); + { + path = expandVolumeName(path); //may block for slow USB sticks and idle HDDs! + + /* need to resolve relative paths: + WINDOWS: + - \\?\-prefix requires absolute names + - Volume Shadow Copy: volume name needs to be part of each file path + - file icon buffer (at least for extensions that are actually read from disk, like "exe") + WINDOWS/LINUX: + - detection of dependent directories, e.g. "\" and "C:\test" */ + path = resolveRelativePath(path); + } //remove trailing slash, unless volume root: - if (std::optional<PathComponents> pc = parsePathComponents(path)) - { - if (pc->relPath.empty()) - path = pc->rootPath; - else - path = appendSeparator(pc->rootPath) + pc->relPath; - } //keep this brace for GCC: -Wparentheses + if (const std::optional<PathComponents> pc = parsePathComponents(path)) + path = appendPath(pc->rootPath, pc->relPath); return path; } diff --git a/zen/resolve_path.h b/zen/resolve_path.h index 4a5fc8fe..bfef087b 100644 --- a/zen/resolve_path.h +++ b/zen/resolve_path.h @@ -7,8 +7,7 @@ #ifndef RESOLVE_PATH_H_817402834713454 #define RESOLVE_PATH_H_817402834713454 -#include <vector> -#include "zstring.h" +#include "file_error.h" namespace zen @@ -24,7 +23,8 @@ Zstring getResolvedFilePath(const Zstring& pathPhrase); //noexcept //macro substitution only Zstring expandMacros(const Zstring& text); -std::vector<Zstring> getFolderPathAliases(const Zstring& folderPathPhrase); //may block for slow USB sticks when resolving [<volume name>] +std::vector<Zstring> getPathPhraseAliases(const Zstring& itemPath); +Zstring makePathPhrase(const Zstring& itemPath); } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 61422eb4..1e4165be 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -8,7 +8,7 @@ #define SCOPE_GUARD_H_8971632487321434 #include <cassert> -#include <exception> +//#include <exception> #include "type_traits.h" #include "legacy_compiler.h" //std::uncaught_exceptions @@ -91,7 +91,7 @@ private: ScopeGuard (const ScopeGuard&) = delete; ScopeGuard& operator=(const ScopeGuard&) = delete; - F fun_; + const F fun_; const int exeptionCount_ = std::uncaught_exceptions(); bool dismissed_ = false; }; diff --git a/zen/serialize.h b/zen/serialize.h index f9677630..b2561808 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -8,9 +8,9 @@ #define SERIALIZE_H_839405783574356 #include <functional> -#include <cstdint> -#include <stdexcept> -#include "string_base.h" +//#include <cstdint> +//#include <stdexcept> +//#include "string_base.h" #include "sys_error.h" //keep header clean from specific stream implementations! (e.g.file_io.h)! used by abstract.h! @@ -206,7 +206,7 @@ void writeArray(BufferedOutputStream& stream, const void* buffer, size_t len) template <class N, class BufferedOutputStream> inline void writeNumber(BufferedOutputStream& stream, const N& num) { - static_assert(IsArithmeticV<N> || std::is_same_v<N, bool> || std::is_enum_v<N>); + static_assert(isArithmetic<N> || std::is_same_v<N, bool> || std::is_enum_v<N>); writeArray(stream, &num, sizeof(N)); } @@ -234,7 +234,7 @@ void readArray(BufferedInputStream& stream, void* buffer, size_t len) //throw Sy template <class N, class BufferedInputStream> inline N readNumber(BufferedInputStream& stream) //throw SysErrorUnexpectedEos { - static_assert(IsArithmeticV<N> || std::is_same_v<N, bool> || std::is_enum_v<N>); + static_assert(isArithmetic<N> || std::is_same_v<N, bool> || std::is_enum_v<N>); N num{}; readArray(stream, &num, sizeof(N)); //throw SysErrorUnexpectedEos return num; diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp index a812d6ae..e64e1e70 100644 --- a/zen/shutdown.cpp +++ b/zen/shutdown.cpp @@ -16,9 +16,9 @@ using namespace zen; void zen::shutdownSystem() //throw FileError { - assert(runningOnMainThread()); - if (runningOnMainThread()) - onSystemShutdownRunTasks(); + assert(runningOnMainThread()); + if (runningOnMainThread()) + onSystemShutdownRunTasks(); try { //https://linux.die.net/man/2/reboot => needs admin rights! diff --git a/zen/socket.h b/zen/socket.h index f9813852..5ece29f8 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -7,7 +7,6 @@ #ifndef SOCKET_H_23498325972583947678456437 #define SOCKET_H_23498325972583947678456437 -#include <zen/zstring.h> #include "sys_error.h" #include <unistd.h> //close #include <sys/socket.h> diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 0d359641..9f7977db 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -10,16 +10,28 @@ #include <set> #include <map> #include <vector> +#include <unordered_set> +#include <unordered_map> #include <memory> #include <cassert> #include <algorithm> #include <optional> -#include "string_traits.h" +#include "type_traits.h" //enhancements for <algorithm> namespace zen { +//unfortunately std::erase_if is useless garbage on GCC 12 (requires non-modifying predicate) +template <class T, class Alloc, class Predicate> +void eraseIf(std::vector<T, Alloc>& v, Predicate p); + +template <class T, class LessType, class Alloc, class Predicate> +void eraseIf(std::set<T, LessType, Alloc>& s, Predicate p); + +template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate> +void eraseIf(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p); + //append STL containers template <class T, class Alloc, class C> void append(std::vector<T, Alloc>& v, const C& c); @@ -104,6 +116,44 @@ SharedRef<T> makeSharedRef(Args&& ... args) { return SharedRef<T>(std::make_shar //######################## implementation ######################## + +template <class T, class Alloc, class Predicate> inline +void eraseIf(std::vector<T, Alloc>& v, Predicate p) +{ + v.erase(std::remove_if(v.begin(), v.end(), p), v.end()); +} + + +namespace impl +{ +template <class S, class Predicate> inline +void setOrMapEraseIf(S& s, Predicate p) +{ + for (auto it = s.begin(); it != s.end();) + if (p(*it)) + s.erase(it++); + else + ++it; +} +} + + +template <class T, class LessType, class Alloc, class Predicate> inline +void eraseIf(std::set<T, LessType, Alloc>& s, Predicate p) { impl::setOrMapEraseIf(s, p); } //don't make this any more generic! e.g. must not compile for std::vector!!! + + +template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate> inline +void eraseIf(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p) { impl::setOrMapEraseIf(m, p); } + + +template <class T, class Hash, class Keyeq, class Alloc, class Predicate> inline +void eraseIf(std::unordered_set<T, Hash, Keyeq, Alloc>& s, Predicate p) { impl::setOrMapEraseIf(s, p); } + + +template <class KeyType, class ValueType, class Hash, class Keyeq, class Alloc, class Predicate> inline +void eraseIf(std::unordered_map<KeyType, ValueType, Hash, Keyeq, Alloc>& m, Predicate p) { impl::setOrMapEraseIf(m, p); } + + template <class T, class Alloc, class C> inline void append(std::vector<T, Alloc>& v, const C& c) { v.insert(v.end(), c.begin(), c.end()); } @@ -249,9 +299,8 @@ void mergeTraversal(Iterator first1, Iterator last1, } -//FNV-1a: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function template <class Num> -class FNV1aHash +class FNV1aHash //FNV-1a: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function { public: FNV1aHash() {} @@ -266,50 +315,13 @@ public: Num get() const { return hashVal_; } private: - static_assert(IsUnsignedIntV<Num>); + static_assert(isUnsignedInt<Num>); static_assert(sizeof(Num) == 4 || sizeof(Num) == 8); static constexpr Num base_ = sizeof(Num) == 4 ? 2166136261U : 14695981039346656037ULL; static constexpr Num prime_ = sizeof(Num) == 4 ? 16777619U : 1099511628211ULL; Num hashVal_ = base_; }; - - -template <class Num, class ByteIterator> inline -Num hashArray(ByteIterator first, ByteIterator last) -{ - using ValType = typename std::iterator_traits<ByteIterator>::value_type; - static_assert(sizeof(ValType) <= sizeof(Num)); - static_assert(IsIntegerV<ValType> || std::is_same_v<ValType, char> || std::is_same_v<ValType, wchar_t>); - - FNV1aHash<Num> hash; - std::for_each(first, last, [&hash](ValType v) { hash.add(v); }); - return hash.get(); -} - - -struct StringHash //support for custom string classes with std::unordered_set/map -{ - using is_transparent = int; //allow heterogenous lookup! - - template <class String> - size_t operator()(const String& str) const - { - const auto* const strFirst = strBegin(str); - return hashArray<size_t>(strFirst, strFirst + strLength(str)); - } -}; - -struct StringEqual -{ - using is_transparent = int; //allow heterogenous lookup! - - template <class String1, class String2> - bool operator()(const String1& lhs, const String2& rhs) const - { - return equalString(lhs, rhs); - } -}; } #endif //STL_TOOLS_H_84567184321434 diff --git a/zen/string_base.h b/zen/string_base.h index 693ce118..ace870b9 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -7,8 +7,8 @@ #ifndef STRING_BASE_H_083217454562342526 #define STRING_BASE_H_083217454562342526 -#include <algorithm> #include <atomic> +#include <utility> //std::exchange #include "string_tools.h" @@ -377,8 +377,8 @@ Zbase<Char, SP>::Zbase(const Zbase<Char, SP>& str) template <class Char, template <class> class SP> inline Zbase<Char, SP>::Zbase(Zbase<Char, SP>&& tmp) noexcept { - rawStr_ = tmp.rawStr_; - tmp.rawStr_ = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! + rawStr_ = std::exchange(tmp.rawStr_, nullptr); + //usually nullptr would violate the class invarants, but it is good enough for the destructor! //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! } @@ -637,8 +637,8 @@ Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept { //don't use swap() but end rawStr_ life time immediately this->destroy(rawStr_); - rawStr_ = tmp.rawStr_; - tmp.rawStr_ = nullptr; + + rawStr_ = std::exchange(tmp.rawStr_, nullptr); return *this; } @@ -653,4 +653,12 @@ void Zbase<Char, SP>::pop_back() } } + +//std::hash specialization in global namespace +template <class Char, template <class> class SP> +struct std::hash<zen::Zbase<Char, SP>> +{ + size_t operator()(const zen::Zbase<Char, SP>& str) const { return zen::hashString<size_t>(str); } +}; + #endif //STRING_BASE_H_083217454562342526 diff --git a/zen/string_tools.h b/zen/string_tools.h index ee4e5613..d3f35ce8 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -11,9 +11,6 @@ #include <cwctype> //iswspace #include <cstdio> //sprintf #include <cwchar> //swprintf -#include <algorithm> -#include <cassert> -#include <vector> #include "stl_tools.h" #include "string_traits.h" #include "legacy_compiler.h" //<charconv> but without the compiler crashes :> @@ -33,25 +30,30 @@ template <class Char> Char asciiToLower(Char c); template <class Char> Char asciiToUpper(Char c); //both S and T can be strings or char/wchar_t arrays or single char/wchar_t -template <class S, class T, typename = std::enable_if_t<IsStringLikeV<S>>> bool contains(const S& str, const T& term); +template <class S, class T, typename = std::enable_if_t<isStringLike<S>/*Astyle hates tripe >*/ >> bool contains(const S& str, const T& term); - template <class S, class T> bool startsWith (const S& str, const T& prefix); - template <class S, class T> bool startsWithAsciiNoCase(const S& str, const T& prefix); +template <class S, class T> bool startsWith (const S& str, const T& prefix); +template <class S, class T> bool startsWithAsciiNoCase(const S& str, const T& prefix); - template <class S, class T> bool endsWith (const S& str, const T& postfix); - template <class S, class T> bool endsWithAsciiNoCase(const S& str, const T& postfix); +template <class S, class T> bool endsWith (const S& str, const T& postfix); +template <class S, class T> bool endsWithAsciiNoCase(const S& str, const T& postfix); - template <class S, class T> bool equalString (const S& lhs, const T& rhs); - template <class S, class T> bool equalAsciiNoCase(const S& lhs, const T& rhs); +template <class S, class T> bool equalString (const S& lhs, const T& rhs); +template <class S, class T> bool equalAsciiNoCase(const S& lhs, const T& rhs); - // template <class S, class T> std::strong_ordering compareString (const S& lhs, const T& rhs); - template <class S, class T> std::weak_ordering compareAsciiNoCase(const S& lhs, const T& rhs); //basic case-insensitive comparison (considering A-Z only!) +//template <class S, class T> std::strong_ordering compareString(const S& lhs, const T& rhs); +template <class S, class T> std::weak_ordering compareAsciiNoCase(const S& lhs, const T& rhs); //basic case-insensitive comparison (considering A-Z only!) - struct LessAsciiNoCase //STL container predicate -{ - template <class S> bool operator()(const S& lhs, const S& rhs) const { return std::is_lt(compareAsciiNoCase(lhs, rhs)); } -}; +//STL container predicates for std::map, std::unordered_set/map +struct StringHash; +struct StringEqual; + +struct LessAsciiNoCase; +struct StringHashAsciiNoCase; +struct StringEqualAsciiNoCase; +template <class Num, class S> Num hashString(const S& str); +template <class Num, class S> Num appendHashString(Num hashVal, const S& str); enum class IfNotFoundReturn { @@ -77,8 +79,11 @@ template <class S> void trim (S& str, bool fromLeft = true, bo template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar); -template <class S, class T, class U> [[nodiscard]] S replaceCpy(S str, const T& oldTerm, const U& newTerm, bool replaceAll = true); -template <class S, class T, class U> void replace (S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); +template <class S, class T, class U> [[nodiscard]] S replaceCpy(S str, const T& oldTerm, const U& newTerm); +template <class S, class T, class U> void replace (S& str, const T& oldTerm, const U& newTerm); + +template <class S, class T, class U> [[nodiscard]] S replaceCpyAsciiNoCase(S str, const T& oldTerm, const U& newTerm); +template <class S, class T, class U> void replaceAsciiNoCase (S& str, const T& oldTerm, const U& newTerm); //high-performance conversion between numbers and strings template <class S, class Num> S numberTo(const Num& number); @@ -173,25 +178,28 @@ template <class Char> inline Char asciiToLower(Char c) { if (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z')) - return static_cast<Char>(c - static_cast<Char>('A') + static_cast<Char>('a')); - return c; + return static_cast<Char>(c - static_cast<Char>('A') + static_cast<Char>('a')); + return c; } - template <class Char> inline - Char asciiToUpper(Char c) +template <class Char> inline +Char asciiToUpper(Char c) { if (static_cast<Char>('a') <= c && c <= static_cast<Char>('z')) - return static_cast<Char>(c - static_cast<Char>('a') + static_cast<Char>('A')); - return c; + return static_cast<Char>(c - static_cast<Char>('a') + static_cast<Char>('A')); + return c; } - namespace impl +namespace impl { -//support embedded 0, unlike strncmp/wcsncmp: -inline std::strong_ordering strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std:: memcmp(ptr1, ptr2, num) <=> 0; } -inline std::strong_ordering strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num) <=> 0; } +template <class Char> inline +bool equalSubstring(const Char* lhs, const Char* rhs, size_t len) +{ + //support embedded 0, unlike strncmp/wcsncmp: + return std::equal(lhs, lhs + len, rhs); +} template <class Char1, class Char2> inline @@ -213,13 +221,14 @@ template <class S, class T> inline bool startsWith(const S& str, const T& prefix) { const size_t pfLen = strLength(prefix); - return strLength(str) >= pfLen && impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen) == std::strong_ordering::equal; + return strLength(str) >= pfLen && impl::equalSubstring(strBegin(str), strBegin(prefix), pfLen); } template <class S, class T> inline bool startsWithAsciiNoCase(const S& str, const T& prefix) { + assert(isAsciiString(str) || isAsciiString(prefix)); const size_t pfLen = strLength(prefix); return strLength(str) >= pfLen && impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen) == std::weak_ordering::equivalent; } @@ -230,7 +239,7 @@ bool endsWith(const S& str, const T& postfix) { const size_t strLen = strLength(str); const size_t pfLen = strLength(postfix); - return strLen >= pfLen && impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == std::strong_ordering::equal; + return strLen >= pfLen && impl::equalSubstring(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen); } @@ -247,19 +256,24 @@ template <class S, class T> inline bool equalString(const S& lhs, const T& rhs) { const size_t lhsLen = strLength(lhs); - return lhsLen == strLength(rhs) && impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen) == std::strong_ordering::equal; + return lhsLen == strLength(rhs) && impl::equalSubstring(strBegin(lhs), strBegin(rhs), lhsLen); } template <class S, class T> inline bool equalAsciiNoCase(const S& lhs, const T& rhs) { + assert(isAsciiString(lhs) || isAsciiString(rhs)); const size_t lhsLen = strLength(lhs); return lhsLen == strLength(rhs) && impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen) == std::weak_ordering::equivalent; } #if 0 +//support embedded 0, unlike strncmp/wcsncmp: +inline std::strong_ordering strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std:: memcmp(ptr1, ptr2, num) <=> 0; } +inline std::strong_ordering strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num) <=> 0; } + template <class S, class T> inline std::strong_ordering compareString(const S& lhs, const T& rhs) { @@ -427,29 +441,22 @@ namespace impl ZEN_INIT_DETECT_MEMBER(append) //either call operator+=(S(str, len)) or append(str, len) -template <class S, class InputIterator, typename = std::enable_if_t<HasMemberV_append<S>>> inline +template <class S, class InputIterator, typename = std::enable_if_t<hasMember_append<S>>> inline void stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); } //inefficient append: keep disabled until really needed -//template <class S, class InputIterator, typename = std::enable_if_t<!HasMemberV_append<S>>> inline +//template <class S, class InputIterator, typename = std::enable_if_t<!hasMember_append<S>>> inline //void stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); } -} - - -template <class S, class T, class U> inline -S replaceCpy(S str, const T& oldTerm, const U& newTerm, bool replaceAll) -{ - replace(str, oldTerm, newTerm, replaceAll); - return str; -} -template <class S, class T, class U> inline -void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll) +template <class S, class T, class U, class CharEq> inline +void replace(S& str, const T& oldTerm, const U& newTerm, CharEq charEqual) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); static_assert(std::is_same_v<GetCharTypeT<T>, GetCharTypeT<U>>); const size_t oldLen = strLength(oldTerm); + const size_t newLen = strLength(newTerm); + //assert(oldLen != 0); -> reasonable check, but challenged by unit-test if (oldLen == 0) return; @@ -457,13 +464,17 @@ void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll) const auto* const oldEnd = oldBegin + oldLen; const auto* const newBegin = strBegin(newTerm); - const auto* const newEnd = newBegin + strLength(newTerm); + const auto* const newEnd = newBegin + newLen; + + using CharType = GetCharTypeT<S>; + if (oldLen == 1 && newLen == 1) //don't use expensive std::search unless required! + return std::replace_if(str.begin(), str.end(), [charEqual, charOld = *oldBegin](CharType c) { return charEqual(c, charOld); }, *newBegin); - auto it = strBegin(str); //don't use str.begin() or wxString will return this wxUni* nonsense! - const auto* const strEnd = it + strLength(str); + auto* it = strBegin(str); //don't use str.begin() or wxString will return this wxUni* nonsense! + auto* const strEnd = it + strLength(str); auto itFound = std::search(it, strEnd, - oldBegin, oldEnd); + oldBegin, oldEnd, charEqual); if (itFound == strEnd) return; //optimize "oldTerm not found" @@ -472,12 +483,13 @@ void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { impl::stringAppend(output, newBegin, newEnd); it = itFound + oldLen; - +#if 0 if (!replaceAll) itFound = strEnd; else +#endif itFound = std::search(it, strEnd, - oldBegin, oldEnd); + oldBegin, oldEnd, charEqual); impl::stringAppend(output, it, itFound); } @@ -485,6 +497,37 @@ void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll) str = std::move(output); } +} + + +template <class S, class T, class U> inline +void replace(S& str, const T& oldTerm, const U& newTerm) +{ impl::replace(str, oldTerm, newTerm, std::equal_to()); } + + +template <class S, class T, class U> inline +S replaceCpy(S str, const T& oldTerm, const U& newTerm) +{ + replace(str, oldTerm, newTerm); + return str; +} + + +template <class S, class T, class U> inline +void replaceAsciiNoCase(S& str, const T& oldTerm, const U& newTerm) +{ + using CharType = GetCharTypeT<S>; + impl::replace(str, oldTerm, newTerm, + [](CharType charL, CharType charR) { return asciiToLower(charL) == asciiToLower(charR); }); +} + + +template <class S, class T, class U> inline +S replaceCpyAsciiNoCase(S str, const T& oldTerm, const U& newTerm) +{ + replaceAsciiNoCase(str, oldTerm, newTerm); + return str; +} template <class Char, class Function> inline @@ -813,9 +856,9 @@ template <class S, class Num> inline S numberTo(const Num& number) { using TypeTag = std::integral_constant<impl::NumberType, - IsSignedIntV <Num> ? impl::NumberType::signedInt : - IsUnsignedIntV<Num> ? impl::NumberType::unsignedInt : - IsFloatV <Num> ? impl::NumberType::floatingPoint : + isSignedInt <Num> ? impl::NumberType::signedInt : + isUnsignedInt<Num> ? impl::NumberType::unsignedInt : + isFloat <Num> ? impl::NumberType::floatingPoint : impl::NumberType::other>; return impl::numberTo<S>(number, TypeTag()); @@ -826,9 +869,9 @@ template <class Num, class S> inline Num stringTo(const S& str) { using TypeTag = std::integral_constant<impl::NumberType, - IsSignedIntV <Num> ? impl::NumberType::signedInt : - IsUnsignedIntV<Num> ? impl::NumberType::unsignedInt : - IsFloatV <Num> ? impl::NumberType::floatingPoint : + isSignedInt <Num> ? impl::NumberType::signedInt : + isUnsignedInt<Num> ? impl::NumberType::unsignedInt : + isFloat <Num> ? impl::NumberType::floatingPoint : impl::NumberType::other>; return impl::stringTo<Num>(str, TypeTag()); @@ -885,6 +928,72 @@ std::string formatAsHexString(const std::string_view& blob) } + + +template <class Num, class S> inline +Num hashString(const S& str) +{ + using CharType = GetCharTypeT<S>; + const auto* const strFirst = strBegin(str); + + FNV1aHash<Num> hash; + std::for_each(strFirst, strFirst + strLength(str), [&hash](CharType c) { hash.add(c); }); + return hash.get(); +} + + +struct StringHash +{ + using is_transparent = int; //allow heterogenous lookup! + + template <class String> + size_t operator()(const String& str) const { return hashString<size_t>(str); } +}; + + +struct StringEqual +{ + using is_transparent = int; //allow heterogenous lookup! + + template <class String1, class String2> + bool operator()(const String1& lhs, const String2& rhs) const { return equalString(lhs, rhs); } +}; + + +struct LessAsciiNoCase +{ + template <class String> + bool operator()(const String& lhs, const String& rhs) const { return std::is_lt(compareAsciiNoCase(lhs, rhs)); } +}; + + +struct StringHashAsciiNoCase +{ + using is_transparent = int; //allow heterogenous lookup! + + template <class String> + size_t operator()(const String& str) const + { + using CharType = GetCharTypeT<String>; + const auto* const strFirst = strBegin(str); + + FNV1aHash<size_t> hash; + std::for_each(strFirst, strFirst + strLength(str), [&hash](CharType c) { hash.add(asciiToLower(c)); }); + return hash.get(); + } +}; + + +struct StringEqualAsciiNoCase +{ + using is_transparent = int; //allow heterogenous lookup! + + template <class String1, class String2> + bool operator()(const String1& lhs, const String2& rhs) const + { + return equalAsciiNoCase(lhs, rhs); + } +}; } #endif //STRING_TOOLS_H_213458973046 diff --git a/zen/string_traits.h b/zen/string_traits.h index ca40f7d6..1a4f4740 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -15,9 +15,9 @@ //uniform access to string-like types, both classes and character arrays namespace zen { -/* IsStringLikeV<>: - IsStringLikeV<const wchar_t*> //equals "true" - IsStringLikeV<const int*> //equals "false" +/* isStringLike<>: + isStringLike<const wchar_t*> //equals "true" + isStringLike<const int*> //equals "false" GetCharTypeT<>: GetCharTypeT<std::wstring> //equals wchar_t @@ -51,7 +51,7 @@ template <class Iterator> auto makeStringView(Iterator first, size_t len); //---------------------- implementation ---------------------- namespace impl { -template<class S, class Char> //test if result of S::c_str() can convert to const Char* +template <class S, class Char> //test if result of S::c_str() can convert to const Char* class HasConversion { using Yes = char[1]; @@ -105,9 +105,9 @@ class StringTraits public: enum { - isStringClass = HasMemberTypeV_value_type<CleanType>&& - HasMemberV_c_str <CleanType>&& - HasMemberV_length <CleanType> + isStringClass = hasMemberType_value_type<CleanType> && + hasMember_c_str <CleanType> && + hasMember_length <CleanType> }; using CharType = typename GetCharTypeImpl<UndecoratedType, isStringClass>::Type; @@ -121,10 +121,10 @@ public: } -template<class T> -constexpr bool IsStringLikeV = impl::StringTraits<T>::isStringLike; +template <class T> +constexpr bool isStringLike = impl::StringTraits<T>::isStringLike; -template<class T> +template <class T> using GetCharTypeT = typename impl::StringTraits<T>::CharType; @@ -184,7 +184,7 @@ inline size_t strLength(const std::basic_string_view<const wchar_t>& ref) { retu template <class S> inline auto strBegin(S&& str) { - static_assert(IsStringLikeV<S>); + static_assert(isStringLike<S>); return impl::strBegin(std::forward<S>(str)); } @@ -192,7 +192,7 @@ auto strBegin(S&& str) template <class S> inline size_t strLength(S&& str) { - static_assert(IsStringLikeV<S>); + static_assert(isStringLike<S>); return impl::strLength(std::forward<S>(str)); } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 4f007047..ada4e358 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -7,9 +7,7 @@ #ifndef SYMLINK_TARGET_H_801783470198357483 #define SYMLINK_TARGET_H_801783470198357483 -#include "scope_guard.h" #include "file_error.h" -#include "file_path.h" #include <unistd.h> #include <stdlib.h> //realpath diff --git a/zen/sys_error.h b/zen/sys_error.h index 99cf9316..6d03f299 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -8,8 +8,8 @@ #define SYS_ERROR_H_3284791347018951324534 #include "scope_guard.h" // -#include "utf.h" //not used by this header, but the "rest of the world" needs it! -#include "i18n.h" // +#include "i18n.h" //not used by this header, but the "rest of the world" needs it! +#include "zstring.h" // #include <glib.h> #include <cerrno> diff --git a/zen/thread.h b/zen/thread.h index 136c7a5c..42fba281 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -9,9 +9,7 @@ #include <thread> #include <future> -#include "scope_guard.h" #include "ring_buffer.h" -#include "string_tools.h" #include "zstring.h" @@ -65,7 +63,7 @@ class ThreadStopRequest {}; //context of worker thread: void interruptionPoint(); //throw ThreadStopRequest -template<class Predicate> +template <class Predicate> void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadStopRequest template <class Rep, class Period> @@ -90,7 +88,7 @@ auto runAsync(Function&& fun); //wait for all with a time limit: return true if *all* results are available! //TODO: use std::when_all when available -template<class InputIterator, class Duration> +template <class InputIterator, class Duration> bool waitForAllTimed(InputIterator first, InputIterator last, const Duration& wait_duration); template<typename T> inline @@ -311,7 +309,7 @@ auto runAsync(Function&& fun) } -template<class InputIterator, class Duration> inline +template <class InputIterator, class Duration> inline bool waitForAllTimed(InputIterator first, InputIterator last, const Duration& duration) { const std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + duration; @@ -417,7 +415,7 @@ public: } //context of worker thread: - template<class Predicate> + template <class Predicate> void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { setConditionVar(&cv); @@ -476,7 +474,7 @@ void interruptionPoint() //throw ThreadStopRequest //context of worker thread: -template<class Predicate> inline +template <class Predicate> inline void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { assert(impl::threadLocalInterruptionStatus); diff --git a/zen/type_traits.h b/zen/type_traits.h index aca80393..28817aad 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -7,18 +7,19 @@ #ifndef TYPE_TRAITS_H_3425628658765467 #define TYPE_TRAITS_H_3425628658765467 +#include <algorithm> #include <type_traits> //https://en.cppreference.com/w/cpp/header/type_traits namespace zen { -template<class T, class...> +template <class T, class...> struct GetFirstOf { using Type = T; }; -template<class... T> using GetFirstOfT = typename GetFirstOf<T...>::Type; +template <class... T> using GetFirstOfT = typename GetFirstOf<T...>::Type; template <class F> @@ -28,61 +29,64 @@ class FunctionReturnType public: using Type = decltype(dummyFun(F())); }; -template<class F> using FunctionReturnTypeT = typename FunctionReturnType<F>::Type; +template <class F> using FunctionReturnTypeT = typename FunctionReturnType<F>::Type; //============================================================================= -template<class T, size_t N> -constexpr size_t arraySize(T (&)[N]) { return N; } - -template<class S, class T, size_t N> -constexpr S arrayAccumulate(T (&arr)[N]) +template <class T, size_t N> +constexpr uint32_t arrayHash(T (&arr)[N]) //don't bother making FNV1aHash constexpr instead { - S sum = 0; - for (size_t i = 0; i < N; ++i) - sum += arr[i]; - return sum; + uint32_t hashVal = 2166136261U; //FNV-1a base + + std::for_each(&arr[0], &arr[N], [&hashVal](T n) + { + //static_assert(isInteger<T> || std::is_same_v<T, char> || std::is_same_v<T, wchar_t>); + static_assert(sizeof(T) <= sizeof(hashVal)); + hashVal ^= static_cast<uint32_t>(n); + hashVal *= 16777619U; //prime + }); + return hashVal; } //Herb Sutter's signedness conversion helpers: https://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ -template<class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); } -template<class T> inline auto makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); } +template <class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); } +template <class T> inline auto makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); } //################# Built-in Types ######################## //unfortunate standardized nonsense: std::is_integral<> includes bool, char, wchar_t! => roll our own: -template<class T> constexpr bool IsUnsignedIntV = std::is_same_v<std::remove_cv_t<T>, unsigned char> || +template <class T> constexpr bool isUnsignedInt = std::is_same_v<std::remove_cv_t<T>, unsigned char> || std::is_same_v<std::remove_cv_t<T>, unsigned short int> || std::is_same_v<std::remove_cv_t<T>, unsigned int> || std::is_same_v<std::remove_cv_t<T>, unsigned long int> || std::is_same_v<std::remove_cv_t<T>, unsigned long long int>; -template<class T> constexpr bool IsSignedIntV = std::is_same_v<std::remove_cv_t<T>, signed char> || +template <class T> constexpr bool isSignedInt = std::is_same_v<std::remove_cv_t<T>, signed char> || std::is_same_v<std::remove_cv_t<T>, short int> || std::is_same_v<std::remove_cv_t<T>, int> || std::is_same_v<std::remove_cv_t<T>, long int> || std::is_same_v<std::remove_cv_t<T>, long long int>; -template<class T> constexpr bool IsIntegerV = IsUnsignedIntV<T> || IsSignedIntV<T>; -template<class T> constexpr bool IsFloatV = std::is_floating_point_v<T>; -template<class T> constexpr bool IsArithmeticV = IsIntegerV<T> || IsFloatV<T>; +template <class T> constexpr bool isInteger = isUnsignedInt<T> || isSignedInt<T>; +template <class T> constexpr bool isFloat = std::is_floating_point_v<T>; +template <class T> constexpr bool isArithmetic = isInteger<T> || isFloat<T>; //################# Class Members ######################## -/* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + HasMember_ +/* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + hasMember_ Example: 1. ZEN_INIT_DETECT_MEMBER(c_str); - 2. HasMemberV_c_str<T> -> use boolean + 2. hasMember_c_str<T> -> use boolean Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_ Example: 1. ZEN_INIT_DETECT_MEMBER2(size, size_t (T::*)() const); - 2. HasMember_size<T>::value -> use as boolean + 2. hasMember_size<T>::value -> use as boolean - Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + HasMemberType_ + Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + hasMemberType_ Example: 1. ZEN_INIT_DETECT_MEMBER_TYPE(value_type); - 2. HasMemberTypeV_value_type<T> -> use as boolean */ + 2. hasMemberType_value_type<T> -> use as boolean */ //########## Sorting ############################## /* @@ -103,7 +107,7 @@ private: }; template <class Predicate> inline -/**/ Predicate makeSortDirection(Predicate pred, std::true_type) { return pred; } +/**/ Predicate makeSortDirection(Predicate pred, std::true_type) { return pred; } template <class Predicate> inline LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { return pred; } @@ -138,10 +142,10 @@ LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { r enum { value = sizeof(hasMember<T>(nullptr)) == sizeof(Yes) }; \ }; \ \ - template<class T> \ + template <class T> \ struct HasMemberImpl_##NAME<false, T> : std::false_type {}; \ \ - template<class T> constexpr bool HasMemberV_##NAME = HasMemberImpl_##NAME<std::is_class_v<T>, T>::value; + template <class T> constexpr bool hasMember_##NAME = HasMemberImpl_##NAME<std::is_class_v<T>, T>::value; //#################################################################### @@ -161,7 +165,7 @@ LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { r enum { value = sizeof(hasMember<U>(nullptr)) == sizeof(Yes) }; \ }; \ \ - template<class T> constexpr bool HasMemberV_##NAME = HasMember_##NAME<T>::value; + template <class T> constexpr bool hasMember_##NAME = HasMember_##NAME<T>::value; //#################################################################### @@ -181,7 +185,7 @@ LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { r enum { value = sizeof(hasMemberType<T>(nullptr)) == sizeof(Yes) }; \ }; \ \ - template<class T> constexpr bool HasMemberTypeV_##TYPENAME = HasMemberType_##TYPENAME<T>::value; + template <class T> constexpr bool hasMemberType_##TYPENAME = HasMemberType_##TYPENAME<T>::value; } @@ -7,8 +7,8 @@ #ifndef UTF_H_01832479146991573473545 #define UTF_H_01832479146991573473545 -#include <cstdint> -#include <iterator> +//#include <cstdint> +//#include <iterator> #include "string_tools.h" //copyStringTo diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index 6f17dc08..e87a284f 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -46,10 +46,10 @@ size_t zen::impl::zlib_compressBound(size_t len) size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw SysError { uLongf bufSize = static_cast<uLong>(trgLen); - const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest, - &bufSize, //uLongf* destLen, - static_cast<const Bytef*>(src), //const Bytef* source, - static_cast<uLong>(srcLen), //uLong sourceLen, + const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest + &bufSize, //uLongf* destLen + static_cast<const Bytef*>(src), //const Bytef* source + static_cast<uLong>(srcLen), //uLong sourceLen level); //int level // Z_OK: success // Z_MEM_ERROR: not enough memory @@ -64,9 +64,9 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw SysError { uLongf bufSize = static_cast<uLong>(trgLen); - const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest, - &bufSize, //uLongf* destLen, - static_cast<const Bytef*>(src), //const Bytef* source, + const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest + &bufSize, //uLongf* destLen + static_cast<const Bytef*>(src), //const Bytef* source static_cast<uLong>(srcLen)); //uLong sourceLen // Z_OK: success // Z_MEM_ERROR: not enough memory diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 635fb47d..76c0a81f 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -5,50 +5,12 @@ // ***************************************************************************** #include "zstring.h" -#include <stdexcept> -#include "utf.h" - #include <glib.h> #include "sys_error.h" using namespace zen; -Zstring getUpperCase(const Zstring& str) -{ - assert(str.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! - - //fast pre-check: - if (isAsciiString(str)) //perf: in the range of 3.5ns - { - Zstring output = str; - for (Zchar& c : output) - c = asciiToUpper(c); - return output; - } - - Zstring strNorm = getUnicodeNormalForm(str); - try - { - static_assert(sizeof(impl::CodePoint) == sizeof(gunichar)); - Zstring output; - output.reserve(strNorm.size()); - - UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); - while (const std::optional<impl::CodePoint> cp = decoder.getNext()) - impl::codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent! - - return output; - - } - catch (SysError&) - { - assert(false); - return str; - } -} - - Zstring getUnicodeNormalForm(const Zstring& str) { //fast pre-check: @@ -75,63 +37,38 @@ Zstring getUnicodeNormalForm(const Zstring& str) } -Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm) +Zstring getUpperCase(const Zstring& str) { - if (oldTerm.empty()) - return str; - - //assert(isAsciiString(oldTerm)); - Zstring output; + assert(str.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! - for (size_t pos = 0;;) + //fast pre-check: + if (isAsciiString(str)) //perf: in the range of 3.5ns { - const size_t posFound = std::search(str.begin() + pos, str.end(), //can't use getUpperCase(): input/output sizes may differ! - oldTerm.begin(), oldTerm.end(), - [](Zchar charL, Zchar charR) { return asciiToUpper(charL) == asciiToUpper(charR); }) - str.begin(); - - if (posFound == str.size()) - { - if (pos == 0) //optimize "oldTerm not found": return ref-counted copy - return str; - output.append(str.begin() + pos, str.end()); - return output; - } - - output.append(str.begin() + pos, str.begin() + posFound); - output += newTerm; - pos = posFound + oldTerm.size(); + Zstring output = str; + for (Zchar& c : output) + c = asciiToUpper(c); + return output; } -} - - -/* https://docs.microsoft.com/de-de/windows/desktop/Intl/handling-sorting-in-your-applications - - Perf test: compare strings 10 mio times; 64 bit build - ----------------------------------------------------- - string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-" - string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf" - Windows (UTF16 wchar_t) - 4 ns | wcscmp - 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase - 314 ns | LCMapString + wmemcmp - - OS X (UTF8 char) - 6 ns | strcmp - 98 ns | strcasecmp - 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs); - 856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive) - 1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive) - ________________________ - time per call | function */ + Zstring strNorm = getUnicodeNormalForm(str); + try + { + static_assert(sizeof(impl::CodePoint) == sizeof(gunichar)); + Zstring output; + output.reserve(strNorm.size()); -std::weak_ordering compareNativePath(const Zstring& lhs, const Zstring& rhs) -{ - assert(lhs.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! - assert(rhs.find(Zchar('\0')) == Zstring::npos); // + UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); + while (const std::optional<impl::CodePoint> cp = decoder.getNext()) + impl::codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent! - return lhs <=> rhs; + return output; + } + catch (SysError&) + { + assert(false); + return str; + } } diff --git a/zen/zstring.h b/zen/zstring.h index 15735cb0..bc7cfb06 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -7,12 +7,13 @@ #ifndef ZSTRING_H_73425873425789 #define ZSTRING_H_73425873425789 +#include <stdexcept> //not used by this header, but the "rest of the world" needs it! +#include "utf.h" // #include "string_base.h" using Zchar = char; #define Zstr(x) x - const Zchar FILE_NAME_SEPARATOR = '/'; //"The reason for all the fuss above" - Loki/SmartPtr @@ -24,12 +25,6 @@ using Zstringc = zen::Zbase<char>; //using Zstringw = zen::Zbase<wchar_t>; -/* Caveat: don't expect input/output string sizes to match: - - different UTF-8 encoding length of upper-case chars - - different number of upper case chars (e.g. ß => "SS" on macOS) - - output is Unicode-normalized */ -Zstring getUpperCase(const Zstring& str); - //Windows, Linux: precomposed //macOS: decomposed Zstring getUnicodeNormalForm(const Zstring& str); @@ -37,105 +32,44 @@ Zstring getUnicodeNormalForm(const Zstring& str); and conformant software should not treat canonically equivalent sequences, whether composed or decomposed or something in between, as different." https://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html */ -struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs); } }; - -Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm); +/* Caveat: don't expect input/output string sizes to match: + - different UTF-8 encoding length of upper-case chars + - different number of upper case chars (e.g. ß => "SS" on macOS) + - output is Unicode-normalized */ +Zstring getUpperCase(const Zstring& str); //------------------------------------------------------------------------------------------ +struct ZstringNorm //use as STL container key: avoid needless Unicode normalizations during std::map<>::find() +{ + /*explicit*/ ZstringNorm(const Zstring& str) : normStr(getUnicodeNormalForm(str)) {} + Zstring normStr; -inline bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { return getUpperCase(lhs) == getUpperCase(rhs); } + std::strong_ordering operator<=>(const ZstringNorm&) const = default; +}; +template<> struct std::hash<ZstringNorm> { size_t operator()(const ZstringNorm& str) const { return std::hash<Zstring>()(str.normStr); } }; + +//struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs); } }; +//------------------------------------------------------------------------------------------ struct ZstringNoCase //use as STL container key: avoid needless upper-case conversions during std::map<>::find() { - ZstringNoCase(const Zstring& str) : upperCase(getUpperCase(str)) {} + /*explicit*/ ZstringNoCase(const Zstring& str) : upperCase(getUpperCase(str)) {} Zstring upperCase; std::strong_ordering operator<=>(const ZstringNoCase&) const = default; }; +template<> struct std::hash<ZstringNoCase> { size_t operator()(const ZstringNoCase& str) const { return std::hash<Zstring>()(str.upperCase); } }; -//------------------------------------------------------------------------------------------ - -/* Compare *local* file paths: - Windows: igore case - Linux: byte-wise comparison - macOS: ignore case + Unicode normalization forms */ -std::weak_ordering compareNativePath(const Zstring& lhs, const Zstring& rhs); - -inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == std::weak_ordering::equivalent; } - -struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return std::is_lt(compareNativePath(lhs, rhs)); } }; +inline bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { return getUpperCase(lhs) == getUpperCase(rhs); } //------------------------------------------------------------------------------------------ std::weak_ordering compareNatural(const Zstring& lhs, const Zstring& rhs); struct LessNaturalSort { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return std::is_lt(compareNatural(lhs, rhs)); } }; -//------------------------------------------------------------------------------------------ - - - -inline -Zstring appendSeparator(Zstring path) //support rvalue references! -{ - if (!zen::endsWith(path, FILE_NAME_SEPARATOR)) - path += FILE_NAME_SEPARATOR; - return path; //returning a by-value parameter => RVO if possible, r-value otherwise! -} - - -inline -Zstring appendPaths(const Zstring& basePath, const Zstring& relPath, Zchar pathSep) -{ - using namespace zen; - - assert(!startsWith(relPath, pathSep) && !endsWith(relPath, pathSep)); - if (relPath.empty()) - return basePath; - if (basePath.empty()) - return relPath; - - if (startsWith(relPath, pathSep)) - { - if (relPath.size() == 1) - return basePath; - - if (endsWith(basePath, pathSep)) - return basePath + (relPath.c_str() + 1); - } - else if (!endsWith(basePath, pathSep)) - { - Zstring output = basePath; - output.reserve(basePath.size() + 1 + relPath.size()); //append all three strings using a single memory allocation - return std::move(output) + pathSep + relPath; // - } - return basePath + relPath; -} - -inline Zstring nativeAppendPaths(const Zstring& basePath, const Zstring& relPath) { return appendPaths(basePath, relPath, FILE_NAME_SEPARATOR); } - - -inline -Zstring getFileExtension(const Zstring& filePath) -{ - //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); - //return afterLast(fileName, Zstr('.'), zen::IfNotFoundReturn::none); - - auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); - if (it == filePath.end()) - it = filePath.begin(); - else - ++it; - - auto it2 = zen::findLast(it, filePath.end(), Zstr('.')); - if (it2 != filePath.end()) - ++it2; - - return Zstring(it2, filePath.end()); -} - - -//common unicode characters +//------------------------------------------------------------------------------------------ +//common Unicode characters const wchar_t EN_DASH = L'\u2013'; const wchar_t EM_DASH = L'\u2014'; const wchar_t* const SPACED_DASH = L" \u2014 "; //using 'EM DASH' |