diff options
author | B. Stack <bgstack15@gmail.com> | 2022-11-22 08:54:34 -0500 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2022-11-22 08:54:34 -0500 |
commit | a034cfca98d4408b175938740628a54f57eb7614 (patch) | |
tree | 501fd78c6276c0be8be8d2c671a58dd0598060b5 /zen | |
parent | add upstream 11.27 (diff) | |
download | FreeFileSync-a034cfca98d4408b175938740628a54f57eb7614.tar.gz FreeFileSync-a034cfca98d4408b175938740628a54f57eb7614.tar.bz2 FreeFileSync-a034cfca98d4408b175938740628a54f57eb7614.zip |
add upstream 11.2811.28
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 8 | ||||
-rw-r--r-- | zen/crc.h | 8 | ||||
-rw-r--r-- | zen/file_access.cpp | 3 | ||||
-rw-r--r-- | zen/file_path.cpp | 6 | ||||
-rw-r--r-- | zen/format_unit.cpp | 21 | ||||
-rw-r--r-- | zen/format_unit.h | 2 | ||||
-rw-r--r-- | zen/http.cpp | 54 | ||||
-rw-r--r-- | zen/http.h | 4 | ||||
-rw-r--r-- | zen/legacy_compiler.h | 11 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 19 | ||||
-rw-r--r-- | zen/process_exec.cpp | 9 | ||||
-rw-r--r-- | zen/serialize.h | 2 | ||||
-rw-r--r-- | zen/stl_tools.h | 69 | ||||
-rw-r--r-- | zen/string_base.h | 91 | ||||
-rw-r--r-- | zen/string_tools.h | 71 | ||||
-rw-r--r-- | zen/sys_info.cpp | 39 | ||||
-rw-r--r-- | zen/sys_version.cpp | 9 | ||||
-rw-r--r-- | zen/zstring.cpp | 52 | ||||
-rw-r--r-- | zen/zstring.h | 4 |
19 files changed, 305 insertions, 177 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index c8a06b78..7258128f 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -20,7 +20,7 @@ template <class T> int sign(T value); //returns one of {-1, 0, 1} template <class T> bool isNull(T value); //...definitively fishy... template <class T, class InputIterator> //precondition: range must be sorted! -auto nearMatch(const T& val, InputIterator first, InputIterator last); +auto roundToGrid(T val, InputIterator first, InputIterator last); template <class N, class D> auto intDivRound(N numerator, D denominator); template <class N, class D> auto intDivCeil (N numerator, D denominator); @@ -122,12 +122,12 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input */ template <class T, class InputIterator> inline -auto nearMatch(const T& val, InputIterator first, InputIterator last) +auto roundToGrid(T val, InputIterator first, InputIterator last) { + assert(std::is_sorted(first, last)); if (first == last) - return static_cast<decltype(*first)>(0); + return static_cast<decltype(*first)>(val); - assert(std::is_sorted(first, last)); InputIterator it = std::lower_bound(first, last, val); if (it == last) return *--last; @@ -12,8 +12,8 @@ namespace zen { -uint16_t getCrc16(const std::string& str); -uint32_t getCrc32(const std::string& str); +uint16_t getCrc16(const std::string_view& str); +uint32_t getCrc32(const std::string_view& str); template <class ByteIterator> uint16_t getCrc16(ByteIterator first, ByteIterator last); template <class ByteIterator> uint32_t getCrc32(ByteIterator first, ByteIterator last); @@ -21,8 +21,8 @@ template <class ByteIterator> uint32_t getCrc32(ByteIterator first, ByteIterator //------------------------- implementation ------------------------------- -inline uint16_t getCrc16(const std::string& str) { return getCrc16(str.begin(), str.end()); } -inline uint32_t getCrc32(const std::string& str) { return getCrc32(str.begin(), str.end()); } +inline uint16_t getCrc16(const std::string_view& str) { return getCrc16(str.begin(), str.end()); } +inline uint32_t getCrc32(const std::string_view& str) { return getCrc32(str.begin(), str.end()); } template <class ByteIterator> inline diff --git a/zen/file_access.cpp b/zen/file_access.cpp index a52ea9b8..01dda68c 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -126,9 +126,6 @@ namespace int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError { const auto& [existingPath, existingType] = getExistingPath(folderPath); //throw FileError - - warn_static("what if existingType is symlink?") - try { struct statfs info = {}; diff --git a/zen/file_path.cpp b/zen/file_path.cpp index 912d5a37..73a3e923 100644 --- a/zen/file_path.cpp +++ b/zen/file_path.cpp @@ -77,6 +77,8 @@ std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) Zstring zen::appendSeparator(Zstring path) //support rvalue references! { + assert(!endsWith(path, FILE_NAME_SEPARATOR == Zstr('/') ? Zstr('\\' ) : Zstr('/' ))); + if (!endsWith(path, FILE_NAME_SEPARATOR)) path += FILE_NAME_SEPARATOR; return path; //returning a by-value parameter => RVO if possible, r-value otherwise! @@ -156,8 +158,8 @@ Zstring zen::getFileExtension(const Zstring& filePath) 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); // + assert(!contains(lhs, Zchar('\0'))); //don't expect embedded nulls! + assert(!contains(rhs, Zchar('\0'))); // return lhs <=> rhs; diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 8b3fccfe..a5dd5152 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -111,8 +111,8 @@ std::wstring roundToBlock(double timeInHigh, const double granularity = 0.1; const double timeInLow = timeInHigh * unitLowPerHigh; const int blockSizeLow = granularity * timeInHigh < 1 ? - numeric::nearMatch(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)): - numeric::nearMatch(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh; + numeric::roundToGrid(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)): + numeric::roundToGrid(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh; const int roundedtimeInLow = std::lround(timeInLow / blockSizeLow) * blockSizeLow; std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh); @@ -149,10 +149,21 @@ std::wstring zen::formatRemainingTime(double timeInSec) } -std::wstring zen::formatPercent0(double fraction) +std::wstring zen::formatProgressPercent(double fraction, int decPlaces) { - return numberTo<std::wstring>(std::lround(fraction * 100)) + L'%'; //need to localize percent!? - //return printNumber<std::wstring>(L"%.2f", fraction * 100) + L'%'; +#if 0 //special case for perf!? + if (decPlaces == 0) + return numberTo<std::wstring>(static_cast<int>(fraction * 100)) + L'%'; +#endif + //round down! don't show 100% when not actually done: https://freefilesync.org/forum/viewtopic.php?t=9781 + const double blocks = std::pow(10, decPlaces); + const double percent = std::floor(fraction * 100 * blocks) / blocks; + + assert(0 <= decPlaces && decPlaces <= 9); + wchar_t format[] = L"%.0f" L"%%" /*literal %: need to localize?*/; + format[2] += static_cast<wchar_t>(std::clamp(decPlaces, 0, 9)); + + return printNumber<std::wstring>(format, percent); } diff --git a/zen/format_unit.h b/zen/format_unit.h index d1ebc28c..5498e8c2 100644 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -18,7 +18,7 @@ namespace zen const int bytesPerKilo = 1000; std::wstring formatFilesizeShort(int64_t filesize); std::wstring formatRemainingTime(double timeInSec); -std::wstring formatPercent0(double fraction /*within [0, 1]*/); //zero decimal places +std::wstring formatProgressPercent(double fraction /*[0, 1]*/, int decPlaces = 0 /*[0, 9]*/); //rounded down! std::wstring formatUtcToLocalTime(time_t utcTime); //like Windows Explorer would... std::wstring formatTwoDigitPrecision (double value); //format with fixed number of digits diff --git a/zen/http.cpp b/zen/http.cpp index 5054ef3f..540b4ef6 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -90,10 +90,10 @@ public: readRequest = [&, postBufStream{MemoryStreamIn(*postBuf)}](std::span<char> buf) mutable { const size_t bytesRead = postBufStream.read(buf.data(), buf.size()); - *postBytesSent += bytesRead; + * postBytesSent += bytesRead; return bytesRead; }; - extraOptions.emplace_back(CURLOPT_POST, 1); + extraOptions.emplace_back(CURLOPT_POST, 1); extraOptions.emplace_back(CURLOPT_POSTFIELDSIZE_LARGE, postBuf->size()); //avoid HTTP chunked transfer encoding? } @@ -162,19 +162,21 @@ public: const std::string headBuf = futHeader.get(); //throw SysError //parse header: https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line - const std::string& statusBuf = beforeFirst(headBuf, "\r\n", IfNotFoundReturn::all); - const std::string& headersBuf = afterFirst (headBuf, "\r\n", IfNotFoundReturn::none); + const std::string_view& statusBuf = beforeFirst<std::string_view>(headBuf, "\r\n", IfNotFoundReturn::all); + const std::string_view& headersBuf = afterFirst <std::string_view>(headBuf, "\r\n", IfNotFoundReturn::none); - const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitOnEmpty::allow); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF + const std::vector<std::string_view> statusItems = splitCpy(statusBuf, ' ', SplitOnEmpty::allow); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/")) throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L'"'); statusCode_ = stringTo<int>(statusItems[1]); - for (const std::string& line : split(headersBuf, '\n', SplitOnEmpty::skip)) //careful: actual line separator is "\r\n"! - responseHeaders_[trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all))] = - /**/ trimCpy(afterFirst (line, ':', IfNotFoundReturn::none)); - + split(headersBuf, '\n', [&](const std::string_view line) + { + if (!line.empty()) //careful: actual line separator is "\r\n"! + responseHeaders_.emplace(trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all)), + trimCpy(afterFirst (line, ':', IfNotFoundReturn::none))); + }); /* let's NOT consider "Content-Length" header: - may be unavailable ("Transfer-Encoding: chunked") - may refer to compressed data size ("Content-Encoding: gzip") */ @@ -274,7 +276,7 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, //encode for "application/x-www-form-urlencoded" -std::string urlencode(const std::string& str) +std::string urlencode(const std::string_view& str) { std::string output; for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/e99d5d39239c611e1e7304e79e88545c4e71a073/ext/standard/url.c#L455 @@ -296,7 +298,7 @@ std::string urlencode(const std::string& str) } -std::string urldecode(const std::string& str) +std::string urldecode(const std::string_view& str) { std::string output; for (size_t i = 0; i < str.size(); ++i) @@ -331,13 +333,16 @@ std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std: } -std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str) +std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string_view str) { std::vector<std::pair<std::string, std::string>> output; - for (const std::string& nvPair : split(str, '&', SplitOnEmpty::skip)) - output.emplace_back(urldecode(beforeFirst(nvPair, '=', IfNotFoundReturn::all)), - urldecode(afterFirst (nvPair, '=', IfNotFoundReturn::none))); + split(str, '&', [&](const std::string_view nvPair) + { + if (!nvPair.empty()) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IfNotFoundReturn::all)), + urldecode(afterFirst (nvPair, '=', IfNotFoundReturn::none))); + }); return output; } @@ -469,16 +474,16 @@ std::wstring zen::formatHttpError(int sc) } -bool zen::isValidEmail(const std::string& email) +bool zen::isValidEmail(const std::string_view& email) { //https://en.wikipedia.org/wiki/Email_address#Syntax //https://tools.ietf.org/html/rfc3696 => note errata! https://www.rfc-editor.org/errata_search.php?rfc=3696 //https://tools.ietf.org/html/rfc5321 - std::string local = beforeLast(email, '@', IfNotFoundReturn::none); - std::string domain = afterLast(email, '@', IfNotFoundReturn::none); + std::string_view local = beforeLast(email, '@', IfNotFoundReturn::none); + std::string_view domain = afterLast(email, '@', IfNotFoundReturn::none); //consider: "t@st"@email.com t\@st@email.com" - auto stripComments = [](std::string& part) + auto stripComments = [](std::string_view& part) { if (startsWith(part, '(')) part = afterFirst(part, ')', IfNotFoundReturn::none); @@ -494,15 +499,16 @@ bool zen::isValidEmail(const std::string& email) return false; //--------------------------------------------------------------------- + //not going to parse and validate this! const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) || contains(local, '\\'); //e.g. "t\@st@email.com" - if (!quoted) //I'm not going to parse and validate this! - for (const std::string& comp : split(local, '.', SplitOnEmpty::allow)) + if (!quoted) + for (const std::string_view& comp : splitCpy(local, '.', SplitOnEmpty::allow)) if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](char c) { - const char printable[] = "!#$%&'*+-/=?^_`{|}~"; + constexpr std::string_view printable("!#$%&'*+-/=?^_`{|}~"); return isAsciiAlpha(c) || isDigit(c) || !isAsciiChar(c) || - std::find(std::begin(printable), std::end(printable), c) != std::end(printable); + contains(printable, c); })) return false; //--------------------------------------------------------------------- @@ -514,7 +520,7 @@ bool zen::isValidEmail(const std::string& email) if (!contains(domain, '.')) return false; - for (const std::string& comp : split(domain, '.', SplitOnEmpty::allow)) + for (const std::string_view& comp : splitCpy(domain, '.', SplitOnEmpty::allow)) if (comp.empty() || comp.size() > 63 || !std::all_of(comp.begin(), comp.end(), [](char c) { return isAsciiAlpha(c) ||isDigit(c) || !isAsciiChar(c) || c == '-'; })) return false; @@ -50,11 +50,11 @@ HttpInputStream sendHttpPost(const Zstring& url, bool internetIsAlive(); //noexcept std::wstring formatHttpError(int httpStatus); -bool isValidEmail(const std::string& email); +bool isValidEmail(const std::string_view& email); std::string htmlSpecialChars(const std::string_view& str); std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs); -std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str); +std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string_view str); } #endif //HTTP_H_879083425703425702 diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 33394de3..8f93153e 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -8,6 +8,7 @@ #define LEGACY_COMPILER_H_839567308565656789 #include <version> //contains all __cpp_lib_<feature> macros +#include <string> /* C++ standard conformance: https://en.cppreference.com/w/cpp/feature_test @@ -25,6 +26,16 @@ namespace std { + + +//W(hy)TF is this not standard? https://stackoverflow.com/a/47735624 +template <class Char, class Traits, class Alloc> inline +basic_string<Char, Traits, Alloc> operator+(basic_string<Char, Traits, Alloc>&& lhs, const basic_string_view<Char> rhs) +{ return move(lhs.append(rhs.begin(), rhs.end())); } //the move *is* needed!!! + +//template <class Char> inline +//basic_string<Char> operator+(const basic_string<Char>& lhs, const basic_string_view<Char>& rhs) { return basic_string<Char>(lhs) + rhs; } +//-> somewhat inefficient: enable + optimize when needed } //--------------------------------------------------------------------------------- diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index 6a5be3e8..1f556656 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -419,30 +419,29 @@ bool zen::isPuttyKeyStream(const std::string& keyStream) std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase) //throw SysError { - std::vector<std::string> lines; + std::vector<std::string_view> lines; - split2(keyStream, isLineBreak<char>, - [&lines](const char* blockFirst, const char* blockLast) + split2(keyStream, isLineBreak<char>, [&lines](const std::string_view block) { - if (blockFirst != blockLast) //consider Windows' <CR><LF> - lines.emplace_back(blockFirst, blockLast); + if (!block.empty()) //consider Windows' <CR><LF> + lines.push_back(block); }); //----------- parse PuTTY ppk structure ---------------------------------- auto itLine = lines.begin(); if (itLine == lines.end() || !startsWith(*itLine, "PuTTY-User-Key-File-2: ")) throw SysError(L"Unknown key file format"); - const std::string algorithm = afterFirst(*itLine, ' ', IfNotFoundReturn::none); + const std::string_view algorithm = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Encryption: ")) throw SysError(L"Unknown key encryption"); - const std::string keyEncryption = afterFirst(*itLine, ' ', IfNotFoundReturn::none); + const std::string_view keyEncryption = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Comment: ")) throw SysError(L"Invalid key comment"); - const std::string comment = afterFirst(*itLine, ' ', IfNotFoundReturn::none); + const std::string_view comment = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Public-Lines: ")) @@ -471,7 +470,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (itLine == lines.end() || !startsWith(*itLine, "Private-MAC: ")) throw SysError(L"Invalid key: MAC missing"); - const std::string macHex = afterFirst(*itLine, ' ', IfNotFoundReturn::none); + const std::string_view macHex = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; //----------- unpack key file elements --------------------- @@ -784,7 +783,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: algorithm == "ecdsa-sha2-nistp384" || algorithm == "ecdsa-sha2-nistp521") { - const std::string algoShort = afterLast(algorithm, '-', IfNotFoundReturn::none); + const std::string_view algoShort = afterLast(algorithm, '-', IfNotFoundReturn::none); if (extractStringPub() != algoShort) throw SysError(L"Invalid public key stream (header)"); diff --git a/zen/process_exec.cpp b/zen/process_exec.cpp index a2c02eb0..ffc90b4f 100644 --- a/zen/process_exec.cpp +++ b/zen/process_exec.cpp @@ -21,19 +21,20 @@ Zstring zen::escapeCommandArg(const Zstring& arg) { //*INDENT-OFF* if not put exactly here, Astyle will seriously mess this .cpp file up! Zstring output; - for (const Zchar c : arg) + for (const char c : arg) switch (c) { + //case ' ': output += "\\ "; break; -> maybe nicer to use quotes instead? case '"': output += "\\\""; break; //Windows: not needed; " cannot be used as file name case '\\': output += "\\\\"; break; //Windows: path separator! => don't escape case '`': output += "\\`"; break; //yes, used in some paths => Windows: no escaping required default: output += c; break; } -//*INDENT-ON* - if (contains(output, Zstr(' '))) - output = Zstr('"') + output + Zstr('"'); //Windows: escaping a single blank instead would not work + if (contains(arg, ' ')) + output = '"' + output + '"'; //caveat: single-quotes not working on macOS if string contains escaped chars! no such issue on Linux return output; +//*INDENT-ON* } diff --git a/zen/serialize.h b/zen/serialize.h index 53a6fc62..dd393422 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -79,7 +79,7 @@ template < class BufferedInputStream> void readArray (BufferedInputSt struct IOCallbackDivider { IOCallbackDivider(const IoCallback& notifyUnbufferedIO, int64_t& totalBytesNotified) : - totalBytesNotified_(totalBytesNotified), + totalBytesNotified_(totalBytesNotified), notifyUnbufferedIO_(notifyUnbufferedIO) { assert(totalBytesNotified == 0); } void operator()(int64_t bytesDelta) //throw X! diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 66af8551..bb005e34 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -58,14 +58,22 @@ void removeDuplicatesStable(std::vector<T, Alloc>& v); template <class BidirectionalIterator, class T> BidirectionalIterator findLast(BidirectionalIterator first, BidirectionalIterator last, const T& value); +template <class RandomAccessIterator1, class RandomAccessIterator2> inline +RandomAccessIterator1 searchFirst(const RandomAccessIterator1 first, const RandomAccessIterator1 last, + const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast); + +template <class RandomAccessIterator1, class RandomAccessIterator2, class IsEq> inline +RandomAccessIterator1 searchFirst(const RandomAccessIterator1 first, const RandomAccessIterator1 last, + const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast, IsEq isEqual); + //replacement for std::find_end taking advantage of bidirectional iterators (and giving the algorithm a reasonable name) -template <class BidirectionalIterator1, class BidirectionalIterator2> -BidirectionalIterator1 searchLast(BidirectionalIterator1 first1, BidirectionalIterator1 last1, - BidirectionalIterator2 first2, BidirectionalIterator2 last2); +template <class RandomAccessIterator1, class RandomAccessIterator2> +RandomAccessIterator1 searchLast(RandomAccessIterator1 first, RandomAccessIterator1 last, + RandomAccessIterator2 needleFirst, RandomAccessIterator2 needleLast); //binary search returning an iterator -template <class Iterator, class T, class CompLess> -Iterator binarySearch(Iterator first, Iterator last, const T& value, CompLess less); +template <class RandomAccessIterator, class T, class CompLess> +RandomAccessIterator binarySearch(RandomAccessIterator first, RandomAccessIterator last, const T& value, CompLess less); //read-only variant of std::merge; input: two sorted ranges template <class Iterator, class FunctionLeftOnly, class FunctionBoth, class FunctionRightOnly, class Compare> @@ -199,10 +207,10 @@ void removeDuplicatesStable(std::vector<T, Alloc>& v) } -template <class Iterator, class T, class CompLess> inline -Iterator binarySearch(Iterator first, Iterator last, const T& value, CompLess less) +template <class RandomAccessIterator, class T, class CompLess> inline +RandomAccessIterator binarySearch(RandomAccessIterator first, RandomAccessIterator last, const T& value, CompLess less) { - static_assert(std::is_same_v<typename std::iterator_traits<Iterator>::iterator_category, std::random_access_iterator_tag>); + static_assert(std::is_same_v<typename std::iterator_traits<RandomAccessIterator>::iterator_category, std::random_access_iterator_tag>); first = std::lower_bound(first, last, value, less); //alternative: std::partition_point if (first != last && !less(value, *first)) @@ -226,29 +234,56 @@ BidirectionalIterator findLast(const BidirectionalIterator first, const Bidirect } -template <class BidirectionalIterator1, class BidirectionalIterator2> inline -BidirectionalIterator1 searchLast(const BidirectionalIterator1 first1, BidirectionalIterator1 last1, - const BidirectionalIterator2 first2, const BidirectionalIterator2 last2) +template <class RandomAccessIterator1, class RandomAccessIterator2> inline +RandomAccessIterator1 searchFirst(const RandomAccessIterator1 first, const RandomAccessIterator1 last, + const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast) +{ + if (needleLast - needleFirst == 1) //don't use expensive std::search unless required! + return std::find(first, last, *needleFirst); + + return std::search(first, last, + needleFirst, needleLast); +} + + +template <class RandomAccessIterator1, class RandomAccessIterator2, class IsEq> inline +RandomAccessIterator1 searchFirst(const RandomAccessIterator1 first, const RandomAccessIterator1 last, + const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast, IsEq isEqual) { - const BidirectionalIterator1 itNotFound = last1; + if (needleLast - needleFirst == 1) //don't use expensive std::search unless required! + return std::find_if(first, last, [needleFirst, isEqual](const auto c){ return isEqual(*needleFirst, c); }); + + return std::search(first, last, + needleFirst, needleLast, isEqual); +} + + +template <class RandomAccessIterator1, class RandomAccessIterator2> inline +RandomAccessIterator1 searchLast(const RandomAccessIterator1 first, RandomAccessIterator1 last, + const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast) +{ + if (needleLast - needleFirst == 1) //fast-path + return findLast(first, last, *needleFirst); + + const RandomAccessIterator1 itNotFound = last; //reverse iteration: 1. check 2. decrement 3. evaluate for (;;) { - BidirectionalIterator1 it1 = last1; - BidirectionalIterator2 it2 = last2; + RandomAccessIterator1 it1 = last; + RandomAccessIterator2 it2 = needleLast; for (;;) { - if (it2 == first2) return it1; - if (it1 == first1) return itNotFound; + if (it2 == needleFirst) return it1; + if (it1 == first) return itNotFound; --it1; --it2; if (*it1 != *it2) break; } - --last1; + --last; } } diff --git a/zen/string_base.h b/zen/string_base.h index a87827e6..3ebf848a 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -92,7 +92,7 @@ protected: //this needs to be checked before writing to "ptr" static bool canWrite(const Char* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; } - static size_t length(const Char* ptr) { return descr(ptr)->length; } + static size_t size(const Char* ptr) { return descr(ptr)->length; } static void setLength(Char* ptr, size_t newLength) { @@ -174,7 +174,7 @@ protected: return d->refCount == 1 && minCapacity <= d->capacity; } - static size_t length(const Char* ptr) { return descr(ptr)->length; } + static size_t size(const Char* ptr) { return descr(ptr)->length; } static void setLength(Char* ptr, size_t newLength) { @@ -228,17 +228,21 @@ public: Zbase(); Zbase(const Char* str) : Zbase(str, str + strLength(str)) {} //implicit conversion from a C-string! Zbase(const Char* str, size_t len) : Zbase(str, str + len) {} + explicit Zbase(const std::basic_string_view<Char> view) : Zbase(view.begin(), view.end()) {} Zbase(size_t count, Char fillChar); - Zbase(const Zbase& str); - Zbase(Zbase&& tmp) noexcept; template <class InputIterator> Zbase(InputIterator first, InputIterator last); + Zbase(const Zbase& str); + Zbase(Zbase&& tmp) noexcept; //explicit Zbase(Char ch); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //-> non-standard extension!!! ~Zbase(); //operator const Char* () const; //NO implicit conversion to a C-string!! Many problems... one of them: if we forget to provide operator overloads, it'll just work with a Char*... + operator std::basic_string_view<Char>() const& noexcept { return {data(), size()}; } + operator std::basic_string_view<Char>() const&& = delete; //=> probably a bug! + //STL accessors using iterator = Char*; using const_iterator = const Char*; @@ -250,25 +254,28 @@ public: iterator end (); const_iterator begin () const { return rawStr_; } - const_iterator end () const { return rawStr_ + length(); } + const_iterator end () const { return rawStr_ + size(); } const_iterator cbegin() const { return begin(); } const_iterator cend () const { return end (); } //std::string functions - size_t length() const; - size_t size () const { return length(); } + size_t length() const { return size(); } + size_t size () const; const Char* c_str() const { return rawStr_; } //C-string format with 0-termination - /**/ Char* data() { return &*begin(); } + const Char* data() const { return &*begin(); } + /**/ Char* data() { return &*begin(); } const Char& operator[](size_t pos) const; /**/ Char& operator[](size_t pos); - bool empty() const { return length() == 0; } + bool empty() const { return size() == 0; } void clear(); +#if 0 //avoid redundant std::string API bloat! size_t find (const Zbase& str, size_t pos = 0) const; // size_t find (const Char* str, size_t pos = 0) const; // size_t find (Char ch, size_t pos = 0) const; //returns "npos" if not found size_t rfind(Char ch, size_t pos = npos) const; // size_t rfind(const Char* str, size_t pos = npos) const; // +#endif //Zbase& replace(size_t pos1, size_t n1, const Zbase& str); void reserve(size_t minCapacity); Zbase& assign(const Char* str, size_t len) { return assign(str, str + len); } @@ -286,7 +293,7 @@ public: Zbase& operator=(const Zbase& str); Zbase& operator=(const Char* str) { return assign(str, strLength(str)); } Zbase& operator=(Char ch) { return assign(&ch, 1); } - Zbase& operator+=(const Zbase& str) { return append(str.c_str(), str.length()); } + Zbase& operator+=(const Zbase& str) { return append(str.c_str(), str.size()); } Zbase& operator+=(const Char* str) { return append(str, strLength(str)); } Zbase& operator+=(Char ch) { return append(&ch, 1); } @@ -355,7 +362,7 @@ template <class Char, template <class> class SP> template <class InputIterator> inline Zbase<Char, SP>::Zbase(InputIterator first, InputIterator last) { - rawStr_ = this->create(std::distance(first, last)); + rawStr_ = this->create(last - first); *std::copy(first, last, rawStr_) = 0; } @@ -394,35 +401,36 @@ Zbase<Char, SP>::~Zbase() } +#if 0 //avoid redundant std::string API bloat! template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::find(const Zbase& str, size_t pos) const +size_t Zbase<Char, SP>::find(const Zbase& str, size_t pos) const //returns "npos" if not found { - assert(pos <= length()); - const size_t len = length(); + assert(pos <= size()); + const size_t len = size(); const Char* thisEnd = begin() + len; //respect embedded 0 - const Char* it = std::search(begin() + std::min(pos, len), thisEnd, + const Char* it = searchFirst(begin() + std::min(pos, len), thisEnd, str.begin(), str.end()); return it == thisEnd ? npos : it - begin(); } template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::find(const Char* str, size_t pos) const +size_t Zbase<Char, SP>::find(const Char* str, size_t pos) const //returns "npos" if not found { - assert(pos <= length()); - const size_t len = length(); + assert(pos <= size()); + const size_t len = size(); const Char* thisEnd = begin() + len; //respect embedded 0 - const Char* it = std::search(begin() + std::min(pos, len), thisEnd, + const Char* it = searchFirst(begin() + std::min(pos, len), thisEnd, str, str + strLength(str)); return it == thisEnd ? npos : it - begin(); } template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::find(Char ch, size_t pos) const +size_t Zbase<Char, SP>::find(Char ch, size_t pos) const //returns "npos" if not found { - assert(pos <= length()); - const size_t len = length(); + assert(pos <= size()); + const size_t len = size(); const Char* thisEnd = begin() + len; //respect embedded 0 const Char* it = std::find(begin() + std::min(pos, len), thisEnd, ch); return it == thisEnd ? npos : it - begin(); @@ -430,10 +438,10 @@ size_t Zbase<Char, SP>::find(Char ch, size_t pos) const template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const +size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const //returns "npos" if not found { - assert(pos == npos || pos <= length()); - const size_t len = length(); + assert(pos == npos || pos <= size()); + const size_t len = size(); const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + 1, len)); const Char* it = findLast(begin(), currEnd, ch); return it == currEnd ? npos : it - begin(); @@ -441,22 +449,23 @@ size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const +size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const //returns "npos" if not found { - assert(pos == npos || pos <= length()); + assert(pos == npos || pos <= size()); const size_t strLen = strLength(str); - const size_t len = length(); + const size_t len = size(); const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + strLen, len)); const Char* it = searchLast(begin(), currEnd, str, str + strLen); return it == currEnd ? npos : it - begin(); } +#endif template <class Char, template <class> class SP> inline void Zbase<Char, SP>::resize(size_t newSize, Char fillChar) { - const size_t oldSize = length(); + const size_t oldSize = size(); if (this->canWrite(rawStr_, newSize)) { if (oldSize < newSize) @@ -485,28 +494,28 @@ void Zbase<Char, SP>::resize(size_t newSize, Char fillChar) template <class Char, template <class> class SP> inline bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { - return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0 + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0 } template <class Char, template <class> class SP> inline bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs) { - return lhs.length() == strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0 + return lhs.size() == strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0 } template <class Char, template <class> class SP> inline -size_t Zbase<Char, SP>::length() const +size_t Zbase<Char, SP>::size() const { - return SP<Char>::length(rawStr_); + return SP<Char>::size(rawStr_); } template <class Char, template <class> class SP> inline const Char& Zbase<Char, SP>::operator[](size_t pos) const { - assert(pos < length()); //design by contract! no runtime check! + assert(pos < size()); //design by contract! no runtime check! return rawStr_[pos]; } @@ -514,8 +523,8 @@ const Char& Zbase<Char, SP>::operator[](size_t pos) const template <class Char, template <class> class SP> inline Char& Zbase<Char, SP>::operator[](size_t pos) { - reserve(length()); //make unshared! - assert(pos < length()); //design by contract! no runtime check! + reserve(size()); //make unshared! + assert(pos < size()); //design by contract! no runtime check! return rawStr_[pos]; } @@ -523,7 +532,7 @@ Char& Zbase<Char, SP>::operator[](size_t pos) template <class Char, template <class> class SP> inline auto Zbase<Char, SP>::begin() -> iterator { - reserve(length()); //make unshared! + reserve(size()); //make unshared! return rawStr_; } @@ -531,7 +540,7 @@ auto Zbase<Char, SP>::begin() -> iterator template <class Char, template <class> class SP> inline auto Zbase<Char, SP>::end() -> iterator { - return begin() + length(); + return begin() + size(); } @@ -557,7 +566,7 @@ void Zbase<Char, SP>::reserve(size_t minCapacity) //make unshared and check capa if (!this->canWrite(rawStr_, minCapacity)) { //allocate a new string - const size_t len = length(); + const size_t len = size(); Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const! std::copy(rawStr_, rawStr_ + len + 1 /*0-termination*/, newStr); @@ -591,7 +600,7 @@ Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last const size_t len = std::distance(first, last); if (len > 0) //avoid making this string unshared for no reason { - const size_t thisLen = length(); + const size_t thisLen = size(); reserve(thisLen + len); //make unshared and check capacity *std::copy(first, last, rawStr_ + thisLen) = 0; @@ -624,7 +633,7 @@ Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept template <class Char, template <class> class SP> inline void Zbase<Char, SP>::pop_back() { - const size_t len = length(); + const size_t len = size(); assert(len > 0); if (len > 0) resize(len - 1); diff --git a/zen/string_tools.h b/zen/string_tools.h index 364a9a26..3975cc92 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -70,11 +70,11 @@ enum class SplitOnEmpty allow, skip }; -template <class S, class Char> [[nodiscard]] std::vector<S> split(const S& str, Char delimiter, SplitOnEmpty soe); +template <class S, class Char, class Function> void split(const S& str, Char delimiter, Function onStringPart); template <class S, class Function1, class Function2> void split2(const S& str, Function1 isDelimiter, Function2 onStringPart); +template <class S, class Char> [[nodiscard]] std::vector<S> splitCpy(const S& str, Char delimiter, SplitOnEmpty soe); -template <class S> [[nodiscard]] S trimCpy(S str, bool fromLeft = true, bool fromRight = true); -template <class Char, class Function> [[nodiscard]] std::pair<Char*, Char*> trimCpy(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar); +template <class S> [[nodiscard]] S trimCpy(const S& str, bool fromLeft = true, bool fromRight = true); template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true); template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar); @@ -315,8 +315,8 @@ bool contains(const S& str, const T& term) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLen; const auto* const termFirst = strBegin(term); - - return std::search(strFirst, strLast, + + return searchFirst(strFirst, strLast, termFirst, termFirst + termLen) != strLast; } @@ -331,7 +331,7 @@ S afterLast(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - + const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) @@ -348,7 +348,7 @@ S beforeLast(const S& str, const T& term, IfNotFoundReturn infr) static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); - + const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); @@ -372,8 +372,8 @@ S afterFirst(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - - const auto* it = std::search(strFirst, strLast, + + const auto* it = searchFirst(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) return infr == IfNotFoundReturn::all ? str : S(); @@ -393,8 +393,8 @@ S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - - auto it = std::search(strFirst, strLast, + + auto it = searchFirst(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) return infr == IfNotFoundReturn::all ? str : S(); @@ -412,7 +412,7 @@ void split2(const S& str, Function1 isDelimiter, Function2 onStringPart) for (;;) { const auto* const blockLast = std::find_if(blockFirst, strEnd, isDelimiter); - onStringPart(blockFirst, blockLast); + onStringPart(makeStringView(blockFirst, blockLast)); if (blockLast == strEnd) return; @@ -422,16 +422,24 @@ void split2(const S& str, Function1 isDelimiter, Function2 onStringPart) } +template <class S, class Char, class Function> inline +void split(const S& str, Char delimiter, Function onStringPart) +{ + static_assert(std::is_same_v<GetCharTypeT<S>, Char>); + split2(str, [delimiter](Char c) { return c == delimiter; }, onStringPart); +} + + template <class S, class Char> inline -std::vector<S> split(const S& str, Char delimiter, SplitOnEmpty soe) +std::vector<S> splitCpy(const S& str, Char delimiter, SplitOnEmpty soe) { static_assert(std::is_same_v<GetCharTypeT<S>, Char>); std::vector<S> output; - split2(str, [delimiter](Char c) { return c == delimiter; }, [&](const Char* blockFirst, const Char* blockLast) + split2(str, [delimiter](Char c) { return c == delimiter; }, [&, soe](std::basic_string_view<Char> block) { - if (blockFirst != blockLast || soe == SplitOnEmpty::allow) - output.emplace_back(blockFirst, blockLast); + if (!block.empty() || soe == SplitOnEmpty::allow) + output.emplace_back(block.data(), block.size()); }); return output; } @@ -474,7 +482,7 @@ void replace(S& str, const T& oldTerm, const U& newTerm, CharEq charEqual) 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, + auto itFound = searchFirst(it, strEnd, oldBegin, oldEnd, charEqual); if (itFound == strEnd) return; //optimize "oldTerm not found" @@ -489,7 +497,7 @@ void replace(S& str, const T& oldTerm, const U& newTerm, CharEq charEqual) itFound = strEnd; else #endif - itFound = std::search(it, strEnd, + itFound = searchFirst(it, strEnd, oldBegin, oldEnd, charEqual); impl::stringAppend(output, it, itFound); @@ -531,8 +539,9 @@ S replaceCpyAsciiNoCase(S str, const T& oldTerm, const U& newTerm) } -template <class Char, class Function> inline -std::pair<Char*, Char*> trimCpy(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar) +template <class Char, class Function> +[[nodiscard]] inline +std::pair<Char*, Char*> trimCpy2(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar) { assert(fromLeft || fromRight); @@ -554,7 +563,7 @@ void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar) assert(fromLeft || fromRight); const auto* const oldBegin = strBegin(str); - const auto& [newBegin, newEnd] = trimCpy(oldBegin, oldBegin + strLength(str), fromLeft, fromRight, trimThisChar); + const auto& [newBegin, newEnd] = trimCpy2(oldBegin, oldBegin + strLength(str), fromLeft, fromRight, trimThisChar); if (newBegin != oldBegin) str = S(newBegin, newEnd); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only @@ -572,11 +581,18 @@ void trim(S& str, bool fromLeft, bool fromRight) template <class S> inline -S trimCpy(S str, bool fromLeft, bool fromRight) +S trimCpy(const S& str, bool fromLeft, bool fromRight) { - //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right! - trim(str, fromLeft, fromRight); - return str; + using CharType = GetCharTypeT<S>; + const auto* const oldBegin = strBegin(str); + const auto* const oldEnd = oldBegin + strLength(str); + + const auto& [newBegin, newEnd] = trimCpy2(oldBegin, oldEnd, fromLeft, fromRight, [](CharType c) { return isWhiteSpace(c); }); + + if (newBegin == oldBegin && newEnd == oldEnd) + return str; + else + return S(newBegin, newEnd - newBegin); } @@ -626,6 +642,7 @@ S printNumber(const T& format, const Num& number) //format a single number using #error refactor? #endif static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); + assert(strBegin(format)[strLength(format)] == 0); //format must be null-terminated! S buf(128, static_cast<GetCharTypeT<S>>('0')); const int charsWritten = impl::saferPrintf(buf.data(), buf.size(), strBegin(format), number); @@ -634,7 +651,7 @@ S printNumber(const T& format, const Num& number) //format a single number using return S(); buf.resize(charsWritten); - return buf; + return buf; } @@ -849,7 +866,7 @@ Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::unsign if (hasMinusSign) { assert(false); - return 0U; + return -makeSigned(number); //at least make some noise } return number; } diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index 87e07f91..5a044c3a 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -131,10 +131,28 @@ ComputerModel zen::getComputerModel() //throw FileError cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0 + trim(cm.model, false, true, [](wchar_t c) { return c == L'_'; }); //e.g. "CBX3___" or just "_" + trim(cm.vendor, false, true, [](wchar_t c) { return c == L'_'; }); //e.g. "DELL__" or just "_" + for (const char* dummyModel : { - "To Be Filled By O.E.M.", "Default string", "$(DEFAULT_STRING)", "Undefined", "empty", "O.E.M", "OEM", "NA", - "System Product Name", "Please change product name", "INVALID", + "Please change product name", + "SYSTEM_PRODUCT_NAME", + "System Product Name", + "To Be Filled By O.E.M.", + "Default string", + "$(DEFAULT_STRING)", + "<null string>", + "Product Name", + "Undefined", + "INVALID", + "Unknow", + "empty", + "O.E.M.", + "O.E.M", + "OEM", + "NA", + ".", }) if (equalAsciiNoCase(cm.model, dummyModel)) { @@ -144,8 +162,21 @@ ComputerModel zen::getComputerModel() //throw FileError for (const char* dummyVendor : { - "To Be Filled By O.E.M.", "Default string", "$(DEFAULT_STRING)", "Undefined", "empty", "O.E.M", "OEM", "NA", - "System manufacturer", "OEM Manufacturer", + "OEM Manufacturer", + "SYSTEM_MANUFACTURER", + "System manufacturer", + "System Manufacter", + "To Be Filled By O.E.M.", + "Default string", + "$(DEFAULT_STRING)", + "Undefined", + "Unknow", + "empty", + "O.E.M.", + "O.E.M", + "OEM", + "NA", + ".", }) if (equalAsciiNoCase(cm.vendor, dummyVendor)) { diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp index d187e0f0..b123ba07 100644 --- a/zen/sys_version.cpp +++ b/zen/sys_version.cpp @@ -47,13 +47,14 @@ OsVersionDetail zen::getOsVersionDetail() //throw SysError } catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError - for (const std::string& line : split(releaseInfo, '\n', SplitOnEmpty::skip)) + split(releaseInfo, '\n', [&](const std::string_view line) + { if (startsWith(line, "NAME=")) osName = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); else if (startsWith(line, "VERSION_ID=")) osVersion = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); - //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" - + //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" + }); trim(osName, true, true, [](char c) { return c == L'"' || c == L'\''; }); trim(osVersion, true, true, [](char c) { return c == L'"' || c == L'\''; }); } @@ -64,7 +65,7 @@ OsVersionDetail zen::getOsVersionDetail() //throw SysError // lsb_release Release is "rolling" // etc/os-release: VERSION_ID is missing - std::vector<std::wstring> verDigits = split<std::wstring>(osVersion, L'.', SplitOnEmpty::allow); //e.g. "7.7.1908" + std::vector<std::wstring_view> verDigits = splitCpy<std::wstring_view>(osVersion, L'.', SplitOnEmpty::allow); //e.g. "7.7.1908" verDigits.resize(2); return OsVersionDetail diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 73f18cd1..59e90956 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -18,7 +18,7 @@ Zstring getUnicodeNormalForm_NonAsciiValidUtf(const Zstring& str, UnicodeNormalF //Example: const char* decomposed = "\x6f\xcc\x81"; //ó // const char* precomposed = "\xc3\xb3"; //ó assert(!isAsciiString(str)); //includes "not-empty" check - assert(str.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! + assert(!contains(str, Zchar('\0'))); //don't expect embedded nulls! try { @@ -75,6 +75,17 @@ Zstring getUnicodeNormalFormNonAscii(const Zstring& str, UnicodeNormalForm form) } +Zstring getUpperCaseAscii(const Zstring& str) +{ + assert(isAsciiString(str)); + + Zstring output = str; + for (Zchar& c : output) //identical to LCMapStringEx(), g_unichar_toupper(), CFStringUppercase() [verified!] + c = asciiToUpper(c); // + return output; +} + + Zstring getUpperCaseNonAscii(const Zstring& str) { Zstring strNorm = getUnicodeNormalFormNonAscii(str, UnicodeNormalForm::native); @@ -102,27 +113,20 @@ Zstring getUpperCaseNonAscii(const Zstring& str) Zstring getUnicodeNormalForm(const Zstring& str, UnicodeNormalForm form) { - //fast pre-check: - if (isAsciiString(str)) //perf: in the range of 3.5ns - return str; static_assert(std::is_same_v<decltype(str), const Zbase<Zchar>&>, "god bless our ref-counting! => save needless memory allocation!"); - return getUnicodeNormalFormNonAscii(str, form); + if (isAsciiString(str)) //fast path: in the range of 3.5ns + return str; + + return getUnicodeNormalFormNonAscii(str, form); //slow path } Zstring getUpperCase(const Zstring& str) { - if (isAsciiString(str)) //fast path: in the range of 3.5ns - { - Zstring output = str; - for (Zchar& c : output) //identical to LCMapStringEx(), g_unichar_toupper(), CFStringUppercase() [verified!] - c = asciiToUpper(c); // - return output; - } - //else: slow path -------------------------------------- - - return getUpperCaseNonAscii(str); + return isAsciiString(str) ? //fast path: in the range of 3.5ns + getUpperCaseAscii(str) : + getUpperCaseNonAscii(str); //slow path } @@ -252,8 +256,11 @@ std::weak_ordering compareNatural(const Zstring& lhs, const Zstring& rhs) std::weak_ordering compareNoCase(const Zstring& lhs, const Zstring& rhs) { + const bool isAsciiL = isAsciiString(lhs); + const bool isAsciiR = isAsciiString(rhs); + //fast path: no memory allocations => ~ 6x speedup - if (isAsciiString(lhs) && isAsciiString(rhs)) + if (isAsciiL && isAsciiR) { const size_t minSize = std::min(lhs.size(), rhs.size()); for (size_t i = 0; i < minSize; ++i) @@ -271,19 +278,19 @@ std::weak_ordering compareNoCase(const Zstring& lhs, const Zstring& rhs) //can't we instead skip isAsciiString() and compare chars as long as isAsciiChar()? // => NOPE! e.g. decomposed Unicode! A seemingly single isAsciiChar() might be followed by a combining character!!! - return getUpperCase(lhs) <=> getUpperCase(rhs); + return (isAsciiL ? getUpperCaseAscii(lhs) : getUpperCaseNonAscii(lhs)) <=> + (isAsciiR ? getUpperCaseAscii(rhs) : getUpperCaseNonAscii(rhs)); } bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { - //fast-path: no need for extra memory allocations const bool isAsciiL = isAsciiString(lhs); const bool isAsciiR = isAsciiString(rhs); - if (isAsciiL != isAsciiR) - return false; - if (isAsciiL) + //fast-path: no extra memory allocations + //caveat: ASCII-char and non-ASCII Unicode *can* compare case-insensitive equal!!! e.g. i and ı https://freefilesync.org/forum/viewtopic.php?t=9718 + if (isAsciiL && isAsciiR) { if (lhs.size() != rhs.size()) return false; @@ -295,5 +302,6 @@ bool equalNoCase(const Zstring& lhs, const Zstring& rhs) return true; } - return getUpperCaseNonAscii(lhs) == getUpperCaseNonAscii(rhs); + return (isAsciiL ? getUpperCaseAscii(lhs) : getUpperCaseNonAscii(lhs)) == + (isAsciiR ? getUpperCaseAscii(rhs) : getUpperCaseNonAscii(rhs)); } diff --git a/zen/zstring.h b/zen/zstring.h index d0a8eb4c..5bcc8b34 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -20,13 +20,13 @@ //a high-performance string for interfacing with native OS APIs in multithreaded contexts using Zstring = zen::Zbase<Zchar>; +using ZstringView = std::basic_string_view<Zchar>; + //for special UI-contexts: guaranteed exponential growth + ref-counting + COW + no SSO overhead using Zstringc = zen::Zbase<char>; //using Zstringw = zen::Zbase<wchar_t>; -//Windows, Linux: precomposed -//macOS: decomposed enum class UnicodeNormalForm { nfc, //precomposed |