/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Essentials/StringUtils.cpp
1 632 строки
35 KB
cvet
Alloc fixes (#192)
27 июл 2026, 14:30
Не верифицирован
27 июл 2026, 14:30
ba3760e
Код
Авторство
О чём код?
// __________ ___ ______ _ // / ____/ __ \____ / (_)___ ___ / ____/___ ____ _(_)___ ___ // / /_ / / / / __ \/ / / __ \/ _ \ / __/ / __ \/ __ `/ / __ \/ _ ` // / __/ / /_/ / / / / / / / / / __/ / /___/ / / / /_/ / / / / / __/ // /_/ \____/_/ /_/_/_/_/ /_/\___/ /_____/_/ /_/\__, /_/_/ /_/\___/ // /____/ // FOnline Engine // https://fonline.ru // https://github.com/cvet/fonline // // MIT License // // Copyright (c) 2006 - 2026, Anton Tsvetinskiy aka cvet <cvet@tut.by> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // #include "StringUtils.h" #include "GlobalData.h" #include "StackTrace.h" #include "UcsTables.inc" #if FO_WINDOWS #define WIN32_LEAN_AND_MEAN #include <Windows.h> #endif #include "WinApiUndef.inc" FO_BEGIN_NAMESPACE struct StrGlobalData { unordered_set<char> TokSym = {'`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', // '+', '-', '=', '|', '\\', '/', '.', ',', '\'', ';', '[', ']', ']', '{', '}', ':', '>', '<', '"'}; }; FO_GLOBAL_DATA(StrGlobalData, StrData); strex::operator string&&() noexcept { FO_NO_STACK_TRACE_ENTRY(); own_storage(); _sv = {}; return std::move(_s); } auto strex::str() noexcept -> string&& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); _sv = {}; return std::move(_s); } // ReSharper disable once CppInconsistentNaming auto strex::c_str() noexcept -> const char* { FO_NO_STACK_TRACE_ENTRY(); own_storage(); return _s.c_str(); } void strex::own_storage() noexcept { FO_NO_STACK_TRACE_ENTRY(); auto view_begin = make_nptr(_sv.data()); auto storage_begin = make_nptr(_s.data()); nptr<const char> storage_end = storage_begin.offset(_s.size()); if (view_begin < storage_begin || !(view_begin < storage_end)) { _s = _sv; } else { if (!(storage_begin == view_begin)) { _s.erase(0, std::bit_cast<size_t>(view_begin.get() - storage_begin.get())); } if (_s.length() != _sv.length()) { _s.resize(_sv.length()); } } _sv = _s; } auto strvex::length() const noexcept -> size_t { FO_NO_STACK_TRACE_ENTRY(); return _sv.length(); } auto strvex::empty() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _sv.empty(); } auto strvex::compare_ignore_case(string_view other) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (_sv.length() != other.length()) { return false; } for (size_t i = 0; i < _sv.length(); i++) { if (std::tolower(_sv[i]) != std::tolower(other[i])) { return false; } } return true; } auto strvex::compare_ignore_case_utf8(string_view other) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (_sv.length() != other.length()) { return false; } if (_sv.empty()) { return true; } for (size_t i = 0; i < _sv.length();) { size_t length = _sv.length() - i; auto text_pos = make_ptr(_sv.data() + i); uint32_t ucs = utf8::Decode(text_pos, length); size_t other_length = other.length() - i; auto other_pos = make_ptr(other.data() + i); uint32_t other_ucs = utf8::Decode(other_pos, other_length); if (!utf8::IsValid(ucs) || !utf8::IsValid(other_ucs)) { return false; } if (length != other_length) { return false; } if (utf8::Lower(ucs) != utf8::Lower(other_ucs)) { return false; } i += length; } return true; } auto strvex::starts_with(char r) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return !_sv.empty() && _sv.front() == r; } auto strvex::starts_with(string_view r) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _sv.length() >= r.length() && _sv.starts_with(r); } auto strvex::ends_with(char r) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return !_sv.empty() && _sv.back() == r; } auto strvex::ends_with(string_view r) const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return _sv.length() >= r.length() && _sv.ends_with(r); } auto strvex::is_valid_utf8() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (_sv.empty()) { return true; } for (size_t i = 0; i < _sv.length();) { size_t length = _sv.length() - i; auto text_pos = make_ptr(_sv.data() + i); uint32_t ucs = utf8::Decode(text_pos, length); if (!utf8::IsValid(ucs)) { return false; } i += length; } return true; } auto strvex::length_utf8() const noexcept -> size_t { FO_NO_STACK_TRACE_ENTRY(); size_t length = 0; for (size_t i = 0; i < _sv.length(); i++) { length += (_sv[i] & 0xC0) != 0x80 ? 1u : 0u; } return length; } auto strvex::substring_until(char separator) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(separator); if (pos != string::npos) { _sv = _sv.substr(0, pos); } return *this; } auto strex::substring_until(char separator) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::substring_until(separator); return *this; } auto strvex::substring_until(string_view separator) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(separator); if (pos != string::npos) { _sv = _sv.substr(0, pos); } return *this; } auto strex::substring_until(string_view separator) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::substring_until(separator); return *this; } auto strvex::substring_after(char separator) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(separator); if (pos != string::npos) { _sv = _sv.substr(pos + 1); } else { _sv = {}; } return *this; } auto strex::substring_after(char separator) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::substring_after(separator); return *this; } auto strvex::substring_after(string_view separator) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(separator); if (pos != string::npos) { _sv = _sv.substr(pos + separator.length()); } else { _sv = {}; } return *this; } auto strex::substring_after(string_view separator) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::substring_after(separator); return *this; } auto strvex::trim() noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); trim(" \n\r\t"); return *this; } auto strex::trim() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::trim(); return *this; } auto strvex::trim(string_view chars) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); ltrim(chars); rtrim(chars); return *this; } auto strex::trim(string_view chars) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::trim(chars); return *this; } auto strvex::ltrim(string_view chars) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto l = _sv.find_first_not_of(chars); if (l == string::npos) { _sv = {}; } else if (l > 0) { _sv = _sv.substr(l); } return *this; } auto strex::ltrim(string_view chars) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::ltrim(chars); return *this; } auto strvex::rtrim(string_view chars) noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto r = _sv.find_last_not_of(chars); if (r < _sv.length() - 1) { _sv = _sv.substr(0, r + 1); } return *this; } auto strex::rtrim(string_view chars) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::rtrim(chars); return *this; } auto strex::erase(char what) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); std::erase(_s, what); _sv = _s; return *this; } auto strex::erase(char begin, char end) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); while (true) { auto begin_pos = _s.find(begin); if (begin_pos == string::npos) { break; } auto end_pos = _s.find(end, begin_pos + 1); if (end_pos == string::npos) { break; } _s.erase(begin_pos, end_pos - begin_pos + 1); } _sv = _s; return *this; } auto strex::replace(char from, char to) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(from); if (pos != string::npos) { own_storage(); auto text_begin = make_nptr(_s.data()); ptr<char> range_begin = text_begin.offset(pos); ptr<char> range_end = text_begin.offset(_s.length()); auto range = std::ranges::subrange(range_begin.get(), range_end.get()); std::ranges::replace(range, from, to); } return *this; } auto strex::replace(char from1, char from2, char to) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); const char from_buf[3] = {from1, from2, 0}; const char to_buf[2] = {to, 0}; replace(from_buf, to_buf); return *this; } auto strex::replace(string_view from, string_view to) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find(from); if (pos != string::npos) { own_storage(); while (pos != string::npos) { _s.replace(pos, from.length(), to); pos += to.length(); pos = _s.find(from, pos); } _sv = _s; } return *this; } auto strex::lower() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); std::ranges::transform(_s, _s.begin(), tolower); return *this; } auto strex::upper() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); std::ranges::transform(_s, _s.begin(), toupper); return *this; } auto strex::lower_utf8() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); for (size_t i = 0; i < _s.length();) { size_t length = _s.length() - i; auto text_begin = make_ptr(_s.c_str()); ptr<const char> text_pos = text_begin.offset(i); uint32_t ucs = utf8::Decode(text_pos, length); ucs = utf8::Lower(ucs); char buf[4]; size_t new_length = utf8::Encode(ucs, buf); _s.replace(i, length, buf, new_length); i += new_length; } _sv = _s; return *this; } auto strex::upper_utf8() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); for (size_t i = 0; i < _s.length();) { size_t length = _s.length() - i; auto text_begin = make_ptr(_s.c_str()); ptr<const char> text_pos = text_begin.offset(i); uint32_t ucs = utf8::Decode(text_pos, length); ucs = utf8::Upper(ucs); char buf[4]; size_t new_length = utf8::Encode(ucs, buf); _s.replace(i, length, buf, new_length); i += new_length; } _sv = _s; return *this; } auto strex::assignVolatile(const volatile char* str, size_t len) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); _s.resize(len); for (size_t i = 0; i < len; i++) { _s[i] = str[i]; } _sv = _s; return *this; } auto strex::join(const_span<string_view> parts) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); size_t total_length = _sv.length() * parts.size(); for (const auto& part : parts) { total_length += part.length(); } string result_str; result_str.reserve(total_length); for (size_t i = 0; i < parts.size(); i++) { if (i > 0) { result_str.append(_sv); } result_str.append(parts[i]); } _s = std::move(result_str); _sv = _s; return *this; } auto strex::join(const_span<string> parts) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); size_t total_length = _sv.length() * parts.size(); for (const auto& part : parts) { total_length += part.length(); } string result_str; result_str.reserve(total_length); for (size_t i = 0; i < parts.size(); i++) { if (i > 0) { result_str.append(_sv); } result_str.append(parts[i]); } _s = std::move(result_str); _sv = _s; return *this; } auto strvex::split(char delimiter) const noexcept -> vector<string_view> { FO_NO_STACK_TRACE_ENTRY(); vector<string_view> result; for (size_t pos = 0;;) { size_t end_pos = _sv.find(delimiter, pos); string_view entry = _sv.substr(pos, end_pos != string::npos ? end_pos - pos : string::npos); if (!entry.empty()) { entry = strvex(entry).trim().strv(); if (!entry.empty()) { result.emplace_back(entry); } } if (end_pos != string::npos) { pos = end_pos + 1; } else { break; } } return result; } auto strex::split(char delimiter) const noexcept -> vector<string> { FO_NO_STACK_TRACE_ENTRY(); auto vec = strvex::split(delimiter); vector<string> result; result.reserve(result.size()); std::ranges::transform(vec, std::back_inserter(result), [](auto&& sv) -> string { return string(sv); }); return result; } auto strvex::split_to_int32(char delimiter) const noexcept -> vector<int32_t> { FO_NO_STACK_TRACE_ENTRY(); vector<int32_t> result; for (size_t pos = 0;;) { size_t end_pos = _sv.find(delimiter, pos); string_view entry = _sv.substr(pos, end_pos != string::npos ? end_pos - pos : string::npos); if (!entry.empty()) { entry = strvex(entry).trim().strv(); if (!entry.empty()) { result.emplace_back(strvex(entry).to_int32()); } } if (end_pos != string::npos) { pos = end_pos + 1; } else { break; } } return result; } auto strvex::tokenize() const noexcept -> vector<string_view> { FO_NO_STACK_TRACE_ENTRY(); vector<string_view> result; string_view trimmed_text = strvex(_sv).trim(); size_t cur_tok_pos = 0; size_t cur_tok_len = 0; auto flush_tok_if_exists = [&]() noexcept { if (cur_tok_len != 0) { string_view tok = trimmed_text.substr(cur_tok_pos, cur_tok_len); result.emplace_back(tok); cur_tok_pos += cur_tok_len; cur_tok_len = 0; } }; for (char ch : trimmed_text) { if (StrData->TokSym.contains(ch)) { flush_tok_if_exists(); cur_tok_len++; flush_tok_if_exists(); } else if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\\') { flush_tok_if_exists(); cur_tok_pos++; } else { cur_tok_len++; } } flush_tok_if_exists(); return result; } auto strex::tokenize() const noexcept -> vector<string> { FO_NO_STACK_TRACE_ENTRY(); auto vec = strvex::tokenize(); vector<string> result; result.reserve(result.size()); std::ranges::transform(vec, std::back_inserter(result), [](auto&& sv) -> string { return string(sv); }); return result; } template<typename T> static auto ConvertToNumber(string_view sv, T& value) noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); size_t len = sv.length(); if (len == 0) { return false; } if (len > strex::MAX_NUMBER_STRING_LENGTH) { return false; } if constexpr (std::integral<T>) { static_assert(std::signed_integral<T>); string_view parse_sv = sv; int32_t base = 10; bool negative = false; if (sv[0] == '-') { if (len >= 2 && sv[1] == '-') { return false; } parse_sv.remove_prefix(1); negative = true; if (len >= 3 && sv[1] == '0' && (sv[2] == 'x' || sv[2] == 'X')) { parse_sv.remove_prefix(2); base = 16; } } else { if (len >= 2 && sv[0] == '0' && (sv[1] == 'x' || sv[1] == 'X')) { parse_sv.remove_prefix(2); base = 16; } } if (parse_sv.empty()) { return false; } std::make_unsigned_t<T> uvalue; auto parse_begin = make_nptr(parse_sv.data()); ptr<const char> parse_end = parse_begin.offset(parse_sv.size()); auto result = std::from_chars(parse_begin.get(), parse_end.get(), uvalue, base); bool success = result.ec == std::errc() && result.ptr == parse_end.get(); bool out_of_range = result.ec == std::errc::result_out_of_range; if (success) { if (negative) { if (uvalue >= static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::min())) { value = std::numeric_limits<T>::min(); } else { value = -static_cast<T>(uvalue); } } else { value = static_cast<T>(uvalue); } return true; } else { // Out of range if (out_of_range) { if (negative) { value = std::numeric_limits<T>::min(); } else { value = static_cast<T>(std::numeric_limits<std::make_unsigned_t<T>>::max()); } return true; } // Try read as float if (base == 10) { if (float64_t fvalue; ConvertToNumber(sv, fvalue)) { value = static_cast<T>(std::clamp(fvalue, static_cast<float64_t>(std::numeric_limits<T>::min()), static_cast<float64_t>(std::numeric_limits<T>::max()))); return true; } } return false; } } else { if (((len >= 2 && sv[0] == '0' && (sv[1] == 'x' || sv[1] == 'X')) || (len >= 3 && sv[0] == '-' && sv[1] == '0' && (sv[2] == 'x' || sv[2] == 'X')))) { // Try read as hex integer if (int64_t ivalue; ConvertToNumber(sv, ivalue)) { value = static_cast<T>(ivalue); return true; } else { return false; } } else { string_view parse_sv = sv; if (parse_sv.back() == 'f') { parse_sv.remove_suffix(1); } if (parse_sv.empty()) { return false; } auto parse_begin = make_nptr(parse_sv.data()); ptr<const char> parse_end = parse_begin.offset(parse_sv.size()); auto result = std::from_chars(parse_begin.get(), parse_end.get(), value); return result.ec == std::errc() && parse_end == result.ptr && std::isfinite(value); } } } auto strvex::is_number() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (_sv.empty()) { return false; } float64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); ignore_unused(value); return success; } auto strvex::is_non_finite_number() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); string_view parse_sv = strvex(_sv).trim(); if (parse_sv.empty()) { return false; } if (parse_sv.front() == '+' || parse_sv.front() == '-') { parse_sv.remove_prefix(1); } if (parse_sv.empty()) { return false; } if (parse_sv.size() > 3 && parse_sv.back() == 'f') { parse_sv.remove_suffix(1); } if (strvex(parse_sv).compare_ignore_case("inf") || strvex(parse_sv).compare_ignore_case("infinity") || strvex(parse_sv).compare_ignore_case("nan")) { return true; } return parse_sv.size() > 4 && strvex(parse_sv.substr(0, 4)).compare_ignore_case("nan(") && parse_sv.back() == ')'; } auto strvex::is_explicit_bool() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (strvex(_sv).trim().compare_ignore_case("true")) { return true; } if (strvex(_sv).trim().compare_ignore_case("false")) { return true; } return false; } auto strvex::to_int32() const noexcept -> int32_t { FO_NO_STACK_TRACE_ENTRY(); int64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); if (success) { constexpr int64_t min = static_cast<int64_t>(std::numeric_limits<int32_t>::min()); constexpr int64_t max = static_cast<int64_t>(std::numeric_limits<int32_t>::max()); return static_cast<int32_t>(std::clamp(value, min, max)); } else { return 0; } } auto strvex::to_uint32() const noexcept -> uint32_t { FO_NO_STACK_TRACE_ENTRY(); int64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); if (success) { constexpr int64_t min = static_cast<int64_t>(std::numeric_limits<uint32_t>::min()); constexpr int64_t max = static_cast<int64_t>(std::numeric_limits<uint32_t>::max()); return static_cast<uint32_t>(std::clamp(value, min, max)); } else { return 0; } } auto strvex::to_int64() const noexcept -> int64_t { FO_NO_STACK_TRACE_ENTRY(); int64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); return success ? value : 0; } auto strvex::to_float32() const noexcept -> float32_t { FO_NO_STACK_TRACE_ENTRY(); float64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); return success ? static_cast<float32_t>(value) : 0.0f; } auto strvex::to_float64() const noexcept -> float64_t { FO_NO_STACK_TRACE_ENTRY(); float64_t value; bool success = ConvertToNumber(strvex(_sv).trim(), value); return success ? value : 0.0; } auto strvex::to_bool() const noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); if (strvex(_sv).trim().compare_ignore_case("true")) { return true; } if (strvex(_sv).trim().compare_ignore_case("false")) { return false; } return to_int64() != 0; } auto strex::format_path() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); trim(); normalize_path_slashes(); own_storage(); // Erase first './' while (_s[0] == '.' && _s[1] == '/') { _s.erase(0, 2); } // Skip first '../' uint32_t back_count = 0; while (_s.length() >= 3 && _s[0] == '.' && _s[1] == '.' && _s[2] == '/') { back_count++; _s.erase(0, 3); } // Replace '/./' to '/' while (true) { auto pos = _s.find("/./"); if (pos == string::npos) { break; } _s.replace(pos, 3, "/"); } // Replace 'folder/../' to '/' while (true) { auto pos = _s.find("/../"); if (pos == string::npos || pos == 0) { break; } auto pos2 = _s.rfind('/', pos - 1); if (pos2 == string::npos) { _s.erase(0, pos + 4); } else { _s.erase(pos2 + 1, pos - pos2 + 3); } } // Apply skipped '../' for (uint32_t i = 0; i < back_count; i++) { _s.insert(0, "../"); } _sv = _s; return *this; } auto strex::extract_dir() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); format_path(); auto pos = _sv.find_last_of('/'); if (pos != string::npos) { _sv = _sv.substr(0, pos); } else { _sv = ""; } return *this; } auto strvex::extract_file_name() noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto pos = _sv.find_last_of("/\\"); if (pos != string::npos) { _sv = _sv.substr(pos + 1); } return *this; } auto strex::extract_file_name() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::extract_file_name(); return *this; } auto strex::get_file_extension() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); auto dot = _sv.find_last_of('.'); _sv = dot != string::npos ? _sv.substr(dot + 1) : ""; lower(); return *this; } auto strvex::erase_file_extension() noexcept -> strvex& { FO_NO_STACK_TRACE_ENTRY(); auto dot = _sv.find_last_of('.'); if (dot != string::npos) { _sv = _sv.substr(0, dot); } return *this; } auto strex::erase_file_extension() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); strvex::erase_file_extension(); return *this; } auto strex::change_file_name(string_view new_name) -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); string ext = strex(_s).get_file_extension().str(); if (!ext.empty()) { strex new_name_with_ext = strex("{}.{}", new_name, ext); _s = strex(_s).extract_dir().combine_path(new_name_with_ext); } else { _s = strex(_s).extract_dir().combine_path(new_name); } _sv = _s; return *this; } auto strex::change_file_extension(string_view new_ext) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); erase_file_extension(); own_storage(); _s.reserve(_s.size() + new_ext.size() + 1); _s += "."; _s += new_ext; _sv = _s; return *this; } auto strex::combine_path(string_view path) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); if (!path.empty()) { own_storage(); if (!_s.empty() && _s.back() != '/' && path.front() != '/') { _s += "/"; } _s += path; _sv = _s; format_path(); } return *this; } auto strex::normalize_path_slashes() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); std::ranges::replace(_s, '\\', '/'); return *this; } auto strex::normalize_line_endings() noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); replace('\r', '\n', '\n'); replace('\r', '\n'); return *this; } #if FO_WINDOWS auto strex::parse_wide_char(ptr<const wchar_t> str) noexcept -> strex& { FO_NO_STACK_TRACE_ENTRY(); own_storage(); size_t wide_len = ::wcslen(str.get()); if (wide_len > static_cast<size_t>(std::numeric_limits<int32_t>::max())) { _sv = _s; return *this; } int32_t len = static_cast<int32_t>(wide_len); if (len != 0) { int32_t output_size = ::WideCharToMultiByte(CP_UTF8, 0, str.get(), len, nullptr, 0, nullptr, nullptr); if (output_size > 0) { auto mem = make_nptr(_malloca(static_cast<size_t>(output_size))); auto buf = mem.reinterpret_as<char>(); if (buf) { int32_t written_size = ::WideCharToMultiByte(CP_UTF8, 0, str.get(), len, buf.get(), output_size, nullptr, nullptr); if (written_size > 0) { _s += string(buf.get(), written_size); } } _freea(buf.get()); } } _sv = _s; return *this; } auto strex::to_wide_char() const noexcept -> wstring { FO_NO_STACK_TRACE_ENTRY(); if (_sv.empty()) { return L""; } if (_sv.length() > static_cast<size_t>(std::numeric_limits<int32_t>::max())) { return {}; } auto source = make_nptr(_sv.data()); int32_t input_len = static_cast<int32_t>(_sv.length()); int32_t wide_len = ::MultiByteToWideChar(CP_UTF8, 0, source.get(), input_len, nullptr, 0); if (wide_len <= 0) { return {}; } wstring result; result.resize(static_cast<size_t>(wide_len)); int32_t written_len = ::MultiByteToWideChar(CP_UTF8, 0, source.get(), input_len, result.data(), wide_len); if (written_len <= 0) { return {}; } if (written_len != wide_len) { result.resize(static_cast<size_t>(written_len)); } return result; } #endif // ReSharper restore CppInconsistentNaming // 0xFFFD - Unicode REPLACEMENT CHARACTER static constexpr uint32_t UNICODE_BAD_CHAR = 0xFFFD; auto utf8::IsValid(uint32_t ucs) noexcept -> bool { FO_NO_STACK_TRACE_ENTRY(); return ucs != UNICODE_BAD_CHAR && ucs <= 0x10FFFF; } auto utf8::DecodeStrNtLen(ptr<const char> str) noexcept -> size_t { FO_NO_STACK_TRACE_ENTRY(); size_t length = 0; if (str[0] != 0) { length++; if (str[1] != 0) { length++; if (str[2] != 0) { length++; if (str[3] != 0) { length++; } } } } return length; } auto utf8::Decode(ptr<const char> str, size_t& length) noexcept -> uint32_t { FO_NO_STACK_TRACE_ENTRY(); if (length == 0) { return UNICODE_BAD_CHAR; } auto make_result = [&length](uint32_t ch, size_t ch_lenght) noexcept -> uint32_t { length = ch_lenght; return ch; }; auto make_error = [&length]() noexcept -> uint32_t { length = 1; return UNICODE_BAD_CHAR; }; auto bytes = str.reinterpret_as<const uint8_t>(); uint8_t c = bytes[0]; if (c < 0x80) { return make_result(c, 1); } if (c < 0xc2) { return make_error(); } if (length < 2) { return make_error(); } if ((bytes[1] & 0xc0) != 0x80) { return make_error(); } if (c < 0xe0) { return make_result(((bytes[0] & 0x1f) << 6) + (bytes[1] & 0x3f), 2); } if (length < 3) { return make_error(); } if (c == 0xe0) { if (bytes[1] < 0xa0) { return make_error(); } if ((bytes[2] & 0xc0) != 0x80) { return make_error(); } return make_result(((bytes[0] & 0x0f) << 12) + ((bytes[1] & 0x3f) << 6) + (bytes[2] & 0x3f), 3); } if (c < 0xf0) { if ((bytes[2] & 0xc0) != 0x80) { return make_error(); } return make_result(((bytes[0] & 0x0f) << 12) + ((bytes[1] & 0x3f) << 6) + (bytes[2] & 0x3f), 3); } if (length < 4) { return make_error(); } if (c == 0xf0) { if (bytes[1] < 0x90) { return make_error(); } if ((bytes[2] & 0xc0) != 0x80 || (bytes[3] & 0xc0) != 0x80) { return make_error(); } return make_result(((bytes[0] & 0x07) << 18) + ((bytes[1] & 0x3f) << 12) + ((bytes[2] & 0x3f) << 6) + (bytes[3] & 0x3f), 4); } if (c < 0xf4) { if ((bytes[2] & 0xc0) != 0x80 || (bytes[3] & 0xc0) != 0x80) { return make_error(); } return make_result(((bytes[0] & 0x07) << 18) + ((bytes[1] & 0x3f) << 12) + ((bytes[2] & 0x3f) << 6) + (bytes[3] & 0x3f), 4); } if (c == 0xf4) { if (bytes[1] > 0x8f) { return make_error(); } if ((bytes[2] & 0xc0) != 0x80 || (bytes[3] & 0xc0) != 0x80) { return make_error(); } return make_result(((bytes[0] & 0x07) << 18) + ((bytes[1] & 0x3f) << 12) + ((bytes[2] & 0x3f) << 6) + (bytes[3] & 0x3f), 4); } return make_error(); } auto utf8::Encode(uint32_t ucs, char (&buf)[4]) noexcept -> size_t { FO_NO_STACK_TRACE_ENTRY(); if (ucs < 0x000080u) { buf[0] = static_cast<char>(ucs); return 1; } if (ucs < 0x000800u) { buf[0] = static_cast<char>(0xc0 | (ucs >> 6)); buf[1] = static_cast<char>(0x80 | (ucs & 0x3F)); return 2; } if (ucs < 0x010000u) { buf[0] = static_cast<char>(0xe0 | (ucs >> 12)); buf[1] = static_cast<char>(0x80 | ((ucs >> 6) & 0x3F)); buf[2] = static_cast<char>(0x80 | (ucs & 0x3F)); return 3; } if (ucs <= 0x0010ffffu) { buf[0] = static_cast<char>(0xf0 | (ucs >> 18)); buf[1] = static_cast<char>(0x80 | ((ucs >> 12) & 0x3F)); buf[2] = static_cast<char>(0x80 | ((ucs >> 6) & 0x3F)); buf[3] = static_cast<char>(0x80 | (ucs & 0x3F)); return 4; } // Encode 0xFFFD buf[0] = static_cast<char>(0xef); buf[1] = static_cast<char>(0xbf); buf[2] = static_cast<char>(0xbd); return 3; } auto utf8::Lower(uint32_t ucs) noexcept -> uint32_t { FO_NO_STACK_TRACE_ENTRY(); uint32_t ret; if (ucs <= 0x02B6) { if (ucs >= 0x0041) { ret = UCS_TABLE_0041[ucs - 0x0041]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x0556) { if (ucs >= 0x0386) { ret = UCS_TABLE_0386[ucs - 0x0386]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x10C5) { if (ucs >= 0x10A0) { ret = UCS_TABLE_10_A0[ucs - 0x10A0]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x1FFC) { if (ucs >= 0x1E00) { ret = UCS_TABLE_1_E00[ucs - 0x1E00]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x2133) { if (ucs >= 0x2102) { ret = UCS_TABLE_2102[ucs - 0x2102]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x24CF) { if (ucs >= 0x24B6) { ret = UCS_TABLE_24_B6[ucs - 0x24B6]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0x33CE) { if (ucs >= 0x33CE) { ret = UCS_TABLE_33_CE[ucs - 0x33CE]; if (ret > 0) { return ret; } } return ucs; } if (ucs <= 0xFF3A) { if (ucs >= 0xFF21) { ret = UCS_TABLE_FF21[ucs - 0xFF21]; if (ret > 0) { return ret; } } return ucs; } return ucs; } struct Utf8Data { Utf8Data() noexcept { FO_STACK_TRACE_ENTRY(); UpperTable.resize(0x10000); for (uint32_t i = 0; i < 0x10000; i++) { UpperTable[i] = static_cast<uint16_t>(i); } for (uint32_t i = 0; i < 0x10000; i++) { uint32_t l = utf8::Lower(i); if (l != i) { UpperTable[l] = static_cast<uint16_t>(i); } } } vector<uint16_t> UpperTable {}; }; FO_GLOBAL_DATA(Utf8Data, Utf8); auto utf8::Upper(uint32_t ucs) noexcept -> uint32_t { FO_NO_STACK_TRACE_ENTRY(); if (ucs >= 0x10000) { return ucs; } return Utf8->UpperTable[ucs]; } FO_END_NAMESPACE