diff options
Diffstat (limited to 'zen')
-rwxr-xr-x | zen/basic_math.h | 51 | ||||
-rwxr-xr-x | zen/dir_watcher.cpp | 10 | ||||
-rwxr-xr-x | zen/file_access.cpp | 142 | ||||
-rwxr-xr-x | zen/file_access.h | 20 | ||||
-rwxr-xr-x | zen/file_io.cpp | 2 | ||||
-rwxr-xr-x | zen/legacy_compiler.h | 36 | ||||
-rwxr-xr-x | zen/recycler.cpp | 2 | ||||
-rwxr-xr-x | zen/ring_buffer.h | 55 | ||||
-rwxr-xr-x | zen/serialize.h | 18 | ||||
-rwxr-xr-x | zen/stl_tools.h | 63 | ||||
-rwxr-xr-x | zen/string_base.h | 4 | ||||
-rwxr-xr-x | zen/string_tools.h | 29 | ||||
-rwxr-xr-x | zen/string_traits.h | 2 | ||||
-rwxr-xr-x | zen/thread.h | 21 | ||||
-rwxr-xr-x | zen/zstring.cpp | 22 | ||||
-rwxr-xr-x | zen/zstring.h | 64 |
16 files changed, 281 insertions, 260 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 0d08f6a6..75f5d3b8 100755 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -21,14 +21,8 @@ namespace numeric template <class T> T abs(T value); template <class T> auto dist(T a, T b); template <class T> int sign(T value); //returns one of {-1, 0, 1} -template <class T> T min(T a, T b, T c); -template <class T> T max(T a, T b, T c); template <class T> bool isNull(T value); -template <class T> void clamp(T& val, T minVal, T maxVal); //make sure minVal <= val && val <= maxVal -template <class T> T clampCpy(T val, T minVal, T maxVal); -//std::clamp() available with C++17 - template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last); @@ -106,51 +100,6 @@ int sign(T value) //returns one of {-1, 0, 1} return value < 0 ? -1 : (value > 0 ? 1 : 0); } - -template <class T> inline -T min(T a, T b, T c) //don't follow std::min's "const T&(const T&, const T&)" API -{ - if (a < b) - return a < c ? a : c; - else - return b < c ? b : c; - //return std::min(std::min(a, b), c); -} - - -template <class T> inline -T max(T a, T b, T c) -{ - if (a > b) - return a > c ? a : c; - else - return b > c ? b : c; - //return std::max(std::max(a, b), c); -} - - -template <class T> inline -T clampCpy(T val, T minVal, T maxVal) -{ - assert(minVal <= maxVal); - if (val < minVal) - return minVal; - else if (val > maxVal) - return maxVal; - return val; -} - -template <class T> inline -void clamp(T& val, T minVal, T maxVal) -{ - assert(minVal <= maxVal); - if (val < minVal) - val = minVal; - else if (val > maxVal) - val = maxVal; -} - - /* part of C++11 now! template <class InputIterator, class Compare> inline diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 2bb3fd26..f5ed0488 100755 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -25,7 +25,7 @@ using namespace zen; struct DirWatcher::Impl { int notifDescr = 0; - std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" + std::map<int, Zstring> watchedPaths; //watch descriptor and (sub-)directory paths -> owned by "notifDescr" }; @@ -90,7 +90,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); } - pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath)); + pimpl_->watchedPaths.emplace(wd, subDirPath); } } @@ -130,12 +130,12 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() if (evt.len != 0) //exclude case: deletion of "self", already reported by parent directory watch { - auto it = pimpl_->watchDescrs.find(evt.wd); - if (it != pimpl_->watchDescrs.end()) + auto it = pimpl_->watchedPaths.find(evt.wd); + if (it != pimpl_->watchedPaths.end()) { //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 = it->second + evt.name; + const Zstring itemPath = appendSeparator(it->second) + evt.name; if ((evt.mask & IN_CREATE) || (evt.mask & IN_MOVED_TO)) diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 88b70b14..82c78760 100755 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -50,32 +50,32 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath) return {}; }; - if (startsWith(itemPath, "/")) - { - if (startsWith(itemPath, "/media/")) - { - //Ubuntu: e.g. /media/zenju/DEVICE_NAME - if (const char* username = ::getenv("USER")) - if (startsWith(itemPath, std::string("/media/") + username + "/")) - return doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + std::optional<PathComponents> pc; //"/media/zenju/" and "/Volumes/" should not fail to parse - //Ubuntu: e.g. /media/cdrom0 - return doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/); - } + if (!pc && startsWith(itemPath, "/mnt/")) //e.g. /mnt/DEVICE_NAME + pc = doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/); - if (startsWith(itemPath, "/run/media/")) //Suse: e.g. /run/media/zenju/DEVICE_NAME - if (const char* username = ::getenv("USER")) - if (startsWith(itemPath, std::string("/run/media/") + username + "/")) - return doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + if (!pc && startsWith(itemPath, "/media/")) //Ubuntu: e.g. /media/zenju/DEVICE_NAME + if (const char* username = ::getenv("USER")) + if (startsWith(itemPath, std::string("/media/") + username + "/")) + pc = doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/); - return doParse(1 /*sepCountVolumeRoot*/, true /*rootWithSep*/); - } + if (!pc && startsWith(itemPath, "/run/media/")) //Centos, Suse: e.g. /run/media/zenju/DEVICE_NAME + if (const char* username = ::getenv("USER")) + if (startsWith(itemPath, std::string("/run/media/") + username + "/")) + pc = doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/); - //we do NOT support relative paths! - return {}; -} + if (!pc && startsWith(itemPath, "/run/user/")) //Ubuntu, e.g.: /run/user/1000/gvfs/smb-share:server=192.168.62.145,share=folder + if (startsWith(itemPath, "/run/user/" + numberTo<std::string>(::getuid()) + "/gvfs/")) //::getuid() never fails + pc = doParse(6 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + if (!pc && startsWith(itemPath, "/")) + pc = doParse(1 /*sepCountVolumeRoot*/, true /*rootWithSep*/); + + return pc; +} + std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) { @@ -108,49 +108,40 @@ ItemType zen::getItemType(const Zstring& itemPath) //throw FileError } -PathStatus zen::getPathStatus(const Zstring& itemPath) //throw FileError +std::optional<ItemType> zen::itemStillExists(const Zstring& itemPath) //throw FileError { - const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); try { - return { getItemType(itemPath), itemPath, {} }; //throw FileError + return getItemType(itemPath); //throw FileError } - catch (FileError&) + catch (const FileError& e) //not existing or access error { + const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); if (!parentPath) //device root throw; //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes: // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE, // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable - } - const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - assert(!itemName.empty()); - - PathStatus ps = getPathStatus(*parentPath); //throw FileError - if (ps.relPath.empty() && - ps.existingType != ItemType::FILE) //obscure, but possible (and not an error) - try - { - traverseFolder(*parentPath, - [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; }, - [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; }, - [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; }, - [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); - } - catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but... - //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found) - - ps.relPath.push_back(itemName); - return ps; -} + const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + assert(!itemName.empty()); -std::optional<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError -{ - const PathStatus ps = getPathStatus(itemPath); //throw FileError - if (ps.relPath.empty()) - return ps.existingType; - return {}; + const std::optional<ItemType> parentType = itemStillExists(*parentPath); //throw FileError + if (parentType && *parentType != ItemType::FILE /*obscure, but possible (and not an error)*/) + try + { + traverseFolder(*parentPath, + [&](const FileInfo& fi) { if (fi.itemName == itemName) throw ItemType::FILE; }, + [&](const FolderInfo& fi) { if (fi.itemName == itemName) throw ItemType::FOLDER; }, + [&](const SymlinkInfo& si) { if (si.itemName == itemName) throw ItemType::SYMLINK; }, + [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); + } + catch (const ItemType&) //finding the item after getItemType() previously failed is exceptional + { + throw e; //yes, slicing + } + return {}; + } } @@ -174,16 +165,6 @@ bool zen::dirAvailable(const Zstring& dirPath) //noexcept } -bool zen::itemNotExisting(const Zstring& itemPath) -{ - try - { - return !getItemTypeIfExists(itemPath); //throw FileError - } - catch (FileError&) { return false; } -} - - namespace { } @@ -209,13 +190,13 @@ uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 } -VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError +FileId /*optional*/ zen::getFileId(const Zstring& itemPath) //throw FileError { struct ::stat fileInfo = {}; if (::stat(itemPath.c_str(), &fileInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat"); - return fileInfo.st_dev; + return extractFileId(fileInfo); } @@ -381,7 +362,7 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr const Zstring parentPathSrc = beforeLast(pathSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); const Zstring parentPathTrg = beforeLast(pathTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); //some (broken) devices may fail to rename case directly: - if (equalLocalPath(parentPathSrc, parentPathTrg)) + if (equalNativePath(parentPathSrc, parentPathTrg)) { if (fileNameSrc == fileNameTrg) return; //non-sensical request @@ -567,8 +548,18 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError { - if (!getParentFolderPath(dirPath)) //device root - return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError + const std::optional<Zstring> parentPath = getParentFolderPath(dirPath); + if (!parentPath) //device root + return; + + try //generally we expect that path already exists (see: ffs_paths.cpp) => check first + { + if (getItemType(dirPath) != ItemType::FILE) //throw FileError + return; + } + catch (FileError&) {} //not yet existing or access error? let's find out... + + createDirectoryIfMissingRecursion(*parentPath); //throw FileError try { @@ -576,18 +567,15 @@ void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw File } catch (FileError&) { - const PathStatus ps = getPathStatus(dirPath); //throw FileError - if (ps.existingType == ItemType::FILE) - throw; + try + { + if (getItemType(dirPath) != ItemType::FILE) //throw FileError + return; //already existing => possible, if createDirectoryIfMissingRecursion() is run in parallel + } + catch (FileError&) {} //not yet existing or access error + //catch (const FileError& e2) { throw FileError(e.toString(), e2.toString()); } -> details needed??? - //ps.relPath.size() == 1 => same createDirectory() call from above? Maybe parent folder was created by parallel thread shortly after failure! - Zstring intermediatePath = ps.existingPath; - for (const Zstring& itemName : ps.relPath) - try - { - createDirectory(intermediatePath = appendSeparator(intermediatePath) + itemName); //throw FileError, ErrorTargetExisting - } - catch (ErrorTargetExisting&) {} //possible, if createDirectoryIfMissingRecursion() is run in parallel + throw; } } diff --git a/zen/file_access.h b/zen/file_access.h index 916f23f5..514d798e 100755 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -30,8 +30,6 @@ std::optional<Zstring> getParentFolderPath(const Zstring& itemPath); //POSITIVE existence checks; if false: 1. item not existing 2. different type 3.device access error or similar bool fileAvailable(const Zstring& filePath); //noexcept bool dirAvailable (const Zstring& dirPath ); // -//NEGATIVE existence checks; if false: 1. item existing 2.device access error or similar -bool itemNotExisting(const Zstring& itemPath); enum class ItemType { @@ -40,17 +38,12 @@ enum class ItemType SYMLINK, }; //(hopefully) fast: does not distinguish between error/not existing -ItemType getItemType (const Zstring& itemPath); //throw FileError +ItemType getItemType(const Zstring& itemPath); //throw FileError //execute potentially SLOW folder traversal but distinguish error/not existing -std::optional<ItemType> getItemTypeIfExists(const Zstring& itemPath); //throw FileError - -struct PathStatus -{ - ItemType existingType; - Zstring existingPath; //itemPath =: existingPath + relPath - std::vector<Zstring> relPath; // -}; -PathStatus getPathStatus(const Zstring& itemPath); //throw FileError +// assumes: - base path still exists +// - all child item path parts must correspond to folder traversal +// => we can conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW! +std::optional<ItemType> itemStillExists(const Zstring& itemPath); //throw FileError enum class ProcSymlink { @@ -62,7 +55,8 @@ void setFileTime(const Zstring& filePath, time_t modTime, ProcSymlink procSl); / //symlink handling: always evaluate target uint64_t getFileSize(const Zstring& filePath); //throw FileError uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available -VolumeId getVolumeId(const Zstring& itemPath); //throw FileError +FileId /*optional*/ getFileId(const Zstring& itemPath); //throw FileError + //get per-user directory designated for temporary files: Zstring getTempFolderPath(); //throw FileError diff --git a/zen/file_io.cpp b/zen/file_io.cpp index df47e4c5..80fb3153 100755 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -128,7 +128,7 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, E return bytesRead; //"zero indicates end of file" } - + size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! { /* diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 16d87c53..bebf5a05 100755 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -7,11 +7,47 @@ #ifndef LEGACY_COMPILER_H_839567308565656789 #define LEGACY_COMPILER_H_839567308565656789 +//#include <span> //requires C++20 + + namespace std { //https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html //https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations + + +//requires C++20! until then, this should suffice... +template <class T> +class span +{ +public: + template <class Iterator> + span(Iterator first, Iterator last) : size_(last - first), data_(first != last ? &*first : nullptr) {} + + template <class Container> + span(Container& cont) : span(cont.begin(), cont.end()) {} + + using iterator = T*; + using const_iterator = const T*; + + iterator begin() { return data_; } + iterator end () { return data_ + size_; } + + const_iterator begin() const { return data_; } + const_iterator end () const { return data_ + size_; } + + const_iterator cbegin() const { return begin(); } + const_iterator cend () const { return end (); } + + T* data() const { return data_; } + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + +private: + const size_t size_; + T* const data_; +}; } #endif //LEGACY_COMPILER_H_839567308565656789 diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 8b4389a7..8d34f262 100755 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -27,7 +27,7 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError if (!::g_file_trash(file, nullptr, &error)) { - const std::optional<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError + const std::optional<ItemType> type = itemStillExists(itemPath); //throw FileError if (!type) return false; diff --git a/zen/ring_buffer.h b/zen/ring_buffer.h index 232e17da..e3dbd55f 100755 --- a/zen/ring_buffer.h +++ b/zen/ring_buffer.h @@ -8,10 +8,7 @@ #define RING_BUFFER_H_01238467085684139453534 #include <cassert> -#include <vector> -#include <stdexcept> #include "scope_guard.h" -#include "string_tools.h" namespace zen @@ -40,13 +37,24 @@ public: ~RingBuffer() { clear(); } - reference front() { return getBufPtr()[bufStart_]; } - const_reference front() const { return getBufPtr()[bufStart_]; } + reference front() { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } + const_reference front() const { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } + + reference back() { checkInvariants(); assert(!empty()); return getBufPtr()[getBufPos(size_ - 1)]; } + const_reference back() const { checkInvariants(); assert(!empty()); return getBufPtr()[getBufPos(size_ - 1)]; } + + template <class U> + void push_front(U&& value) + { + reserve(size_ + 1); //throw ? + ::new (getBufPtr() + getBufPos(capacity_ - 1)) T(std::forward<U>(value)); //throw ? + ++size_; + bufStart_ = getBufPos(capacity_ - 1); + } template <class U> void push_back(U&& value) { - checkInvariants(); reserve(size_ + 1); //throw ? ::new (getBufPtr() + getBufPos(size_)) T(std::forward<U>(value)); //throw ? ++size_; @@ -54,15 +62,21 @@ public: void pop_front() { - checkInvariants(); - if (empty()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - front().~T(); - ++bufStart_; --size_; - if (size_ == 0 || bufStart_ == capacity_) + if (size_ == 0) + bufStart_ = 0; + else + bufStart_ = getBufPos(1); + } + + void pop_back() + { + back().~T(); + --size_; + + if (size_ == 0) bufStart_ = 0; } @@ -80,8 +94,6 @@ public: template <class Iterator> void insert_back(Iterator first, Iterator last) //throw ? (strong exception-safety!) { - checkInvariants(); - const size_t len = last - first; reserve(size_ + len); //throw ? @@ -100,10 +112,8 @@ public: void extract_front(Iterator first, Iterator last) //throw ? strongly exception-safe! (but only basic exception safety for [first, last) range) { checkInvariants(); - const size_t len = last - first; - if (size_ < len) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + assert(size_ >= len); const size_t frontSize = std::min(len, capacity_ - bufStart_); @@ -113,13 +123,12 @@ public: std::destroy(getBufPtr() + bufStart_, getBufPtr() + bufStart_ + frontSize); std::destroy(getBufPtr(), getBufPtr() + len - frontSize); - bufStart_ += len; - size_ -= len; + size_ -= len; if (size_ == 0) bufStart_ = 0; - else if (bufStart_ >= capacity_) - bufStart_ -= capacity_; + else + bufStart_ = getBufPos(len); } void swap(RingBuffer& other) @@ -132,6 +141,8 @@ public: void reserve(size_t minSize) //throw ? (strong exception-safety!) { + checkInvariants(); + if (minSize > capacity_) { const size_t newCapacity = std::max(minSize + minSize / 2, minSize); //no minimum capacity: just like std::vector<> implementation @@ -205,7 +216,7 @@ private: struct FreeStoreDelete { void operator()(std::byte* p) const { ::operator delete (p); } }; - T* getBufPtr() { return reinterpret_cast<T*>(rawMem_.get()); } + /**/ T* getBufPtr() { return reinterpret_cast<T*>(rawMem_.get()); } const T* getBufPtr() const { return reinterpret_cast<T*>(rawMem_.get()); } //unlike pure std::uninitialized_move, this one allows for strong exception-safety! diff --git a/zen/serialize.h b/zen/serialize.h index d34b61b2..8b4c58ea 100755 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -36,20 +36,20 @@ public: using iterator = std::vector<std::byte>::iterator; using const_iterator = std::vector<std::byte>::const_iterator; - iterator begin() { return buffer_->begin(); } - iterator end () { return buffer_->end (); } + iterator begin() { return buffer_.ref().begin(); } + iterator end () { return buffer_.ref().end (); } - const_iterator begin() const { return buffer_->begin(); } - const_iterator end () const { return buffer_->end (); } + const_iterator begin() const { return buffer_.ref().begin(); } + const_iterator end () const { return buffer_.ref().end (); } - void resize(size_t len) { buffer_->resize(len); } - size_t size() const { return buffer_->size(); } - bool empty() const { return buffer_->empty(); } + void resize(size_t len) { buffer_.ref().resize(len); } + size_t size() const { return buffer_.ref().size(); } + bool empty() const { return buffer_.ref().empty(); } - inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer_ == *rhs.buffer_; } + inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return lhs.buffer_.ref() == rhs.buffer_.ref(); } private: - std::shared_ptr<std::vector<std::byte>> buffer_ = std::make_shared<std::vector<std::byte>>(); //always bound! + SharedRef<std::vector<std::byte>> buffer_ = makeSharedRef<std::vector<std::byte>>(); //perf: shared_ptr indirection irrelevant: less than 1% slower! }; diff --git a/zen/stl_tools.h b/zen/stl_tools.h index c3a9bf8f..d8bda888 100755 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -11,10 +11,11 @@ #include <map> #include <vector> #include <memory> +#include <cassert> #include <algorithm> #include <optional> #include "string_traits.h" -#include "build_info.h" +//#include "build_info.h" //enhancements for <algorithm> @@ -22,13 +23,13 @@ namespace zen { //erase selected elements from any container: template <class T, class Alloc, class Predicate> -void erase_if(std::vector<T, Alloc>& v, Predicate p); +void eraseIf(std::vector<T, Alloc>& v, Predicate p); template <class T, class LessType, class Alloc, class Predicate> -void erase_if(std::set<T, LessType, Alloc>& s, Predicate p); +void eraseIf(std::set<T, LessType, Alloc>& s, Predicate p); template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate> -void erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p); +void eraseIf(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p); //append STL containers template <class T, class Alloc, class C> @@ -48,14 +49,14 @@ void removeDuplicates(std::vector<T, Alloc>& v, CompLess less); //binary search returning an iterator template <class Iterator, class T, class CompLess> -Iterator binary_search(Iterator first, Iterator last, const T& value, CompLess less); +Iterator binarySearch(Iterator first, Iterator last, const T& value, CompLess less); template <class BidirectionalIterator, class T> -BidirectionalIterator find_last(BidirectionalIterator first, BidirectionalIterator last, const T& value); +BidirectionalIterator findLast(BidirectionalIterator first, BidirectionalIterator last, const T& value); //replacement for std::find_end taking advantage of bidirectional iterators (and giving the algorithm a reasonable name) template <class BidirectionalIterator1, class BidirectionalIterator2> -BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1, +BidirectionalIterator1 searchLast(BidirectionalIterator1 first1, BidirectionalIterator1 last1, BidirectionalIterator2 first2, BidirectionalIterator2 last2); template <class Num, class ByteIterator> Num hashBytes (ByteIterator first, ByteIterator last); @@ -74,17 +75,49 @@ struct StringHash }; -//why, oh wy is there no std::optional<T>::get()??? +//why, oh why is there no std::optional<T>::get()??? template <class T> inline T* get( std::optional<T>& opt) { return opt ? &*opt : nullptr; } template <class T> inline const T* get(const std::optional<T>& opt) { return opt ? &*opt : nullptr; } +//=========================================================================== +template <class T> class SharedRef; +template <class T, class... Args> SharedRef<T> makeSharedRef(Args&& ... args); + +template <class T> +class SharedRef //why is there no std::shared_ref??? +{ +public: + SharedRef() = delete; //no suprise memory allocations => always construct with makeSharedRef() + + template <class U> + SharedRef(const SharedRef<U>& other) : ref_(other.ref_) {} + + /**/ T& ref() { return *ref_; }; + const T& ref() const { return *ref_; }; + + std::shared_ptr<T> ptr() { return ref_; }; + +private: + explicit SharedRef(std::shared_ptr<T>&& ptr) : ref_(std::move(ptr)) { assert(ref_); } + + template <class U, class... Args> friend SharedRef<U> makeSharedRef(Args&& ... args); + template <class U> friend class SharedRef; + + std::shared_ptr<T> ref_; //always bound +}; + +template <class T, class... Args> inline +SharedRef<T> makeSharedRef(Args&&... args) { return SharedRef<T>(std::make_shared<T>(std::forward<Args>(args)...)); } +//=========================================================================== + + //######################## implementation ######################## template <class T, class Alloc, class Predicate> inline -void erase_if(std::vector<T, Alloc>& v, Predicate p) +void eraseIf(std::vector<T, Alloc>& v, Predicate p) { v.erase(std::remove_if(v.begin(), v.end(), p), v.end()); } @@ -93,7 +126,7 @@ void erase_if(std::vector<T, Alloc>& v, Predicate p) namespace impl { template <class S, class Predicate> inline -void set_or_map_erase_if(S& s, Predicate p) +void setOrMapEraseIf(S& s, Predicate p) { for (auto it = s.begin(); it != s.end();) if (p(*it)) @@ -105,11 +138,11 @@ void set_or_map_erase_if(S& s, Predicate p) template <class T, class LessType, class Alloc, class Predicate> inline -void erase_if(std::set<T, LessType, Alloc>& s, Predicate p) { impl::set_or_map_erase_if(s, p); } //don't make this any more generic! e.g. must not compile for std::vector!!! +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 erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p) { impl::set_or_map_erase_if(m, p); } +void eraseIf(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p) { impl::setOrMapEraseIf(m, p); } template <class T, class Alloc, class C> inline @@ -147,7 +180,7 @@ void removeDuplicates(std::vector<T, Alloc>& v) template <class Iterator, class T, class CompLess> inline -Iterator binary_search(Iterator first, Iterator last, const T& value, CompLess less) +Iterator binarySearch(Iterator first, Iterator last, const T& value, CompLess less) { static_assert(std::is_same_v<typename std::iterator_traits<Iterator>::iterator_category, std::random_access_iterator_tag>); @@ -160,7 +193,7 @@ Iterator binary_search(Iterator first, Iterator last, const T& value, CompLess l template <class BidirectionalIterator, class T> inline -BidirectionalIterator find_last(const BidirectionalIterator first, const BidirectionalIterator last, const T& value) +BidirectionalIterator findLast(const BidirectionalIterator first, const BidirectionalIterator last, const T& value) { for (BidirectionalIterator it = last; it != first;) //reverse iteration: 1. check 2. decrement 3. evaluate { @@ -174,7 +207,7 @@ BidirectionalIterator find_last(const BidirectionalIterator first, const Bidirec template <class BidirectionalIterator1, class BidirectionalIterator2> inline -BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, BidirectionalIterator1 last1, +BidirectionalIterator1 searchLast(const BidirectionalIterator1 first1, BidirectionalIterator1 last1, const BidirectionalIterator2 first2, const BidirectionalIterator2 last2) { const BidirectionalIterator1 itNotFound = last1; diff --git a/zen/string_base.h b/zen/string_base.h index 9632eba4..91a6d5bd 100755 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -410,7 +410,7 @@ size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const assert(pos == npos || pos <= length()); const size_t len = length(); const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + 1, len)); - const Char* it = find_last(begin(), currEnd, ch); + const Char* it = findLast(begin(), currEnd, ch); return it == currEnd ? npos : it - begin(); } @@ -422,7 +422,7 @@ size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const const size_t strLen = strLength(str); const size_t len = length(); const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + strLen, len)); - const Char* it = search_last(begin(), currEnd, + const Char* it = searchLast(begin(), currEnd, str, str + strLen); return it == currEnd ? npos : it - begin(); } diff --git a/zen/string_tools.h b/zen/string_tools.h index 657c70d5..8579a460 100755 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -104,26 +104,19 @@ template <class T, class S> T copyStringTo(S&& str); //---------------------- implementation ---------------------- -template <> inline -bool isWhiteSpace(char c) -{ - assert(c != 0); //std C++ does not consider 0 as white space - //caveat 1: std::isspace() takes an int, but expects an unsigned char - //caveat 2: some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\xa0" (MSVC) - return static_cast<unsigned char>(c) < 128 && - std::isspace(static_cast<unsigned char>(c)) != 0; -} - -template <> inline -bool isWhiteSpace(wchar_t c) +template <class Char> inline +bool isWhiteSpace(Char c) { + static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); assert(c != 0); //std C++ does not consider 0 as white space - return std::iswspace(c) != 0; + return c == static_cast<Char>(' ') || (static_cast<Char>('\t') <= c && c <= static_cast<Char>('\r')); + //following std::isspace() for default locale but without the interface insanity: + // - std::isspace() takes an int, but expects an unsigned char + // - some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\xa0" (MSVC) } - template <class Char> inline -bool isDigit(Char c) //similar to implmenetation of std::isdigit()! +bool isDigit(Char c) //similar to implementation of std::isdigit()! { static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); return static_cast<Char>('0') <= c && c <= static_cast<Char>('9'); @@ -306,7 +299,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - const auto* it = search_last(strFirst, strLast, + const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) return rv == IF_MISSING_RETURN_ALL ? str : S(); @@ -327,7 +320,7 @@ S beforeLast(const S& str, const T& term, FailureReturnVal rv) const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - const auto* it = search_last(strFirst, strLast, + const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) return rv == IF_MISSING_RETURN_ALL ? str : S(); @@ -725,7 +718,7 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i number += c - static_cast<CharType>('0'); } else //rest of string should contain whitespace only, it's NOT a bug if there is something else! - break; //assert(std::all_of(iter, last, &isWhiteSpace<CharType>)); -> this is NO assert situation + break; //assert(std::all_of(iter, last, isWhiteSpace<CharType>)); -> this is NO assert situation } return number; } diff --git a/zen/string_traits.h b/zen/string_traits.h index cd7dbf1b..93cfd81c 100755 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -53,7 +53,7 @@ public: private: const size_t len_; - Char* str_; + Char* const str_; }; diff --git a/zen/thread.h b/zen/thread.h index 7f3d216c..809bc771 100755 --- a/zen/thread.h +++ b/zen/thread.h @@ -90,10 +90,10 @@ bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0 //wait until first job is successful or all failed: substitute until std::when_any is available template <class T> -class GetFirstResult +class AsyncFirstResult { public: - GetFirstResult(); + AsyncFirstResult(); template <class Fun> void addJob(Fun&& f); //f must return a std::optional<T> containing a value if successful @@ -161,12 +161,15 @@ public: ThreadGroup& operator=(ThreadGroup&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! //context of controlling OR worker thread, non-blocking: - void run(Function&& wi /*should throw ThreadInterruption when needed*/) + void run(Function&& wi /*should throw ThreadInterruption when needed*/, bool insertFront = false) { { std::lock_guard<std::mutex> dummy(workLoad_->lock); - workLoad_->tasks.push_back(std::move(wi)); + if (insertFront) + workLoad_->tasks.push_front(std::move(wi)); + else + workLoad_->tasks.push_back(std::move(wi)); const size_t tasksPending = ++(workLoad_->tasksPending); if (worker_.size() < std::min(tasksPending, threadCountMax_)) @@ -318,7 +321,7 @@ bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& template <class T> -class GetFirstResult<T>::AsyncResult +class AsyncFirstResult<T>::AsyncResult { public: //context: worker threads @@ -361,12 +364,12 @@ private: template <class T> inline -GetFirstResult<T>::GetFirstResult() : asyncResult_(std::make_shared<AsyncResult>()) {} +AsyncFirstResult<T>::AsyncFirstResult() : asyncResult_(std::make_shared<AsyncResult>()) {} template <class T> template <class Fun> inline -void GetFirstResult<T>::addJob(Fun&& f) //f must return a std::optional<T> containing a value on success +void AsyncFirstResult<T>::addJob(Fun&& f) //f must return a std::optional<T> containing a value on success { std::thread t([asyncResult = this->asyncResult_, f = std::forward<Fun>(f)] { asyncResult->reportFinished(f()); }); ++jobsTotal_; @@ -376,11 +379,11 @@ void GetFirstResult<T>::addJob(Fun&& f) //f must return a std::optional<T> conta template <class T> template <class Duration> inline -bool GetFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult_->waitForResult(jobsTotal_, duration); } +bool AsyncFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult_->waitForResult(jobsTotal_, duration); } template <class T> inline -std::optional<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); } +std::optional<T> AsyncFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); } //------------------------------------------------------------------------------------------ diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 68609030..f8a34045 100755 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -77,22 +77,19 @@ Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const { if (oldTerm.empty()) return str; - - Zstring strU = str; - Zstring oldU = oldTerm; - - for (Zchar& c : strU) c = asciiToUpper(c); //can't use makeUpperCopy(): input/output sizes may differ! - for (Zchar& c : oldU) c = asciiToUpper(c); // Zstring output; for (size_t pos = 0;;) { - const size_t posFound = strU.find(oldU, pos); - if (posFound == Zstring::npos) + const size_t posFound = std::search(str.begin() + pos, str.end(), //can't use makeUpperCopy(): 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; + return str; output.append(str.begin() + pos, str.end()); return output; } @@ -126,7 +123,7 @@ OS X (UTF8 char) ________________________ time per call | function */ -int compareLocalPath(const Zstring& lhs, const Zstring& rhs) +int 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); // @@ -250,8 +247,3 @@ int compareNatural(const Zstring& lhs, const Zstring& rhs) } } - - -warn_static("clean up implementation of these two:") -//template <> inline bool isWhiteSpace(char c) -//template <> inline bool isWhiteSpace(wchar_t c) diff --git a/zen/zstring.h b/zen/zstring.h index 20cf968d..9fecdce3 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -35,24 +35,18 @@ Zstring getUnicodeNormalForm(const Zstring& str); Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm); +struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs);} }; + //------------------------------------------------------------------------------------------ -//inline -//int compareNoCase(const Zstring& lhs, const Zstring& rhs) -//{ -// return zen::compareString(makeUpperCopy(lhs), makeUpperCopy(rhs)); -// //avoid eager optimization bugs: e.g. "if (isAsciiString()) compareAsciiNoCase()" might model a different order! -//} inline bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { return makeUpperCopy(lhs) == makeUpperCopy(rhs); } struct ZstringNoCase //use as STL container key: avoid needless upper-case conversions during std::map<>::find() { - ZstringNoCase(const Zstring& str) : upperCase(makeUpperCopy(str)) {} - Zstring upperCase; + ZstringNoCase(const Zstring& str) : upperCase(makeUpperCopy(str)) {} + Zstring upperCase; }; -inline bool operator<(const ZstringNoCase& lhs, const ZstringNoCase& rhs) { return lhs.upperCase < rhs.upperCase; } - -//struct LessNoCase { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareNoCase(lhs, rhs) < 0; } }; +inline bool operator<(const ZstringNoCase& lhs, const ZstringNoCase& rhs) { return lhs.upperCase < rhs.upperCase; } //------------------------------------------------------------------------------------------ @@ -60,11 +54,11 @@ inline bool operator<(const ZstringNoCase& lhs, const ZstringNoCase& rhs) { retu // Windows: igore case // Linux: byte-wise comparison // macOS: igore case + Unicode normalization forms -int compareLocalPath(const Zstring& lhs, const Zstring& rhs); +int compareNativePath(const Zstring& lhs, const Zstring& rhs); -inline bool equalLocalPath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs) == 0; } +inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == 0; } -struct LessLocalPath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareLocalPath(lhs, rhs) < 0; } }; +struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareNativePath(lhs, rhs) < 0; } }; //------------------------------------------------------------------------------------------ int compareNatural(const Zstring& lhs, const Zstring& rhs); @@ -73,11 +67,7 @@ struct LessNaturalSort { bool operator()(const Zstring& lhs, const Zstring rhs) //------------------------------------------------------------------------------------------ warn_static("get rid:") -inline int compareFilePath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs); } - -inline bool equalFilePath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs) == 0; } - -struct LessFilePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareLocalPath(lhs, rhs) < 0; } }; +inline bool equalFilePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == 0; } //------------------------------------------------------------------------------------------ @@ -92,18 +82,50 @@ Zstring appendSeparator(Zstring path) //support rvalue references! 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, zen::IF_MISSING_RETURN_ALL); //return afterLast(fileName, Zstr('.'), zen::IF_MISSING_RETURN_NONE); - auto it = zen::find_last(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); + auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); if (it == filePath.end()) it = filePath.begin(); else ++it; - auto it2 = zen::find_last(it, filePath.end(), Zstr('.')); + auto it2 = zen::findLast(it, filePath.end(), Zstr('.')); if (it2 != filePath.end()) ++it2; |