mirror of https://github.com/orefkov/simstr.git
7662 lines
372 KiB
C++
7662 lines
372 KiB
C++
/*
|
||
* (c) Проект "SimStr", Александр Орефков orefkov@gmail.com
|
||
* ver. 1.2.4
|
||
* Классы для работы со строками
|
||
* (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com
|
||
* ver. 1.2.4
|
||
* Classes for working with strings
|
||
*/
|
||
|
||
/*!
|
||
* @ru @mainpage Библиотека simstr.
|
||
* @include{doc} "../readme_ru.md"
|
||
* @page overview Обзор
|
||
* @includedoc{doc} "overview_ru.md"
|
||
* @en @mainpage Simstr lib.
|
||
* @include{doc} "../readme.md"
|
||
* @page overview Overview
|
||
* @includedoc{doc} "overview.md"
|
||
*/
|
||
#pragma once
|
||
#ifndef __has_declspec_attribute
|
||
#define __has_declspec_attribute(x) 0
|
||
#endif
|
||
|
||
#ifdef SIMSTR_IN_SHARED
|
||
#if defined(_MSC_VER) || (defined(__clang__) && __has_declspec_attribute(dllexport))
|
||
#ifdef SIMSTR_EXPORT
|
||
#define SIMSTR_API __declspec(dllexport)
|
||
#else
|
||
#define SIMSTR_API __declspec(dllimport)
|
||
#endif
|
||
#elif (defined(__GNUC__) || defined(__GNUG__)) && defined(SIMSTR_EXPORT)
|
||
#define SIMSTR_API __attribute__((visibility("default")))
|
||
#else
|
||
#define SIMSTR_API
|
||
#endif
|
||
#else
|
||
#define SIMSTR_API
|
||
#endif
|
||
const bool isWindowsOs = // NOLINT
|
||
#ifdef _WIN32
|
||
true
|
||
#else
|
||
false
|
||
#endif
|
||
;
|
||
const bool isx64 = sizeof(void*) == 8; // NOLINT
|
||
|
||
#ifdef _MSC_VER
|
||
#define _no_unique_address msvc::no_unique_address
|
||
#define decl_empty_bases __declspec(empty_bases)
|
||
#else
|
||
#define _no_unique_address no_unique_address
|
||
#define decl_empty_bases
|
||
#endif
|
||
|
||
#if defined __has_builtin
|
||
# if __has_builtin (__builtin_mul_overflow) && __has_builtin (__builtin_add_overflow)
|
||
# define HAS_BUILTIN_OVERFLOW
|
||
# endif
|
||
#endif
|
||
|
||
#include "strexpr.h"
|
||
|
||
#include <cstddef>
|
||
#include <string>
|
||
#include <type_traits>
|
||
#include <vector>
|
||
#include <format>
|
||
#include <unordered_map>
|
||
#include <tuple>
|
||
#include <limits>
|
||
#include <cstdint>
|
||
#include <atomic>
|
||
#include <memory>
|
||
#include <string.h>
|
||
#include <iostream>
|
||
|
||
#ifdef _WIN32
|
||
#include <stdio.h>
|
||
#endif
|
||
|
||
#ifdef _MSC_VER
|
||
// warning C4201 : nonstandard extension used : nameless struct / union
|
||
#pragma warning(disable : 4201)
|
||
#endif
|
||
|
||
namespace simstr {
|
||
|
||
template<typename T>
|
||
struct unicode_traits {}; // NOLINT
|
||
|
||
template<>
|
||
struct unicode_traits<u8s> {
|
||
// Эти операции с utf-8 могут изменить длину строки
|
||
// Поэтому их специализации отличаются
|
||
// В функцию помимо текста и адреса буфера для записи передается размер буфера
|
||
// Возвращает длину получающейся строки.
|
||
// Если получающеюся строка не влезает в отведенный буфер, указатели устанавливаются на последние
|
||
// обработанные символы, для повторного возобновления работы,
|
||
// а для оставшихся символов считается нужный размер буфера.
|
||
// These utf-8 operations can change the length of the string
|
||
// Therefore their specializations are different
|
||
// In addition to the text and address of the buffer for writing, the buffer size is passed to the function
|
||
// Returns the length of the resulting string.
|
||
// If the resulting string does not fit into the allocated buffer, pointers are set to the last
|
||
// processed characters to resume work again,
|
||
// and for the remaining characters the required buffer size is calculated.
|
||
static SIMSTR_API size_t upper(const u8s*& src, size_t lenStr, u8s*& dest, size_t lenBuf);
|
||
static SIMSTR_API size_t lower(const u8s*& src, size_t len, u8s*& dest, size_t lenBuf);
|
||
|
||
static SIMSTR_API int compareiu(const u8s* text1, size_t len1, const u8s* text2, size_t len2);
|
||
|
||
static SIMSTR_API size_t hashia(const u8s* src, size_t l);
|
||
static SIMSTR_API size_t hashiu(const u8s* src, size_t l);
|
||
};
|
||
|
||
template<>
|
||
struct unicode_traits<u16s> {
|
||
static SIMSTR_API void upper(const u16s* src, size_t len, u16s* dest);
|
||
static SIMSTR_API void lower(const u16s* src, size_t len, u16s* dest);
|
||
|
||
static SIMSTR_API int compareiu(const u16s* text1, size_t len1, const u16s* text2, size_t len2);
|
||
static SIMSTR_API size_t hashia(const u16s* src, size_t l);
|
||
static SIMSTR_API size_t hashiu(const u16s* src, size_t l);
|
||
};
|
||
|
||
template<>
|
||
struct unicode_traits<u32s> {
|
||
static SIMSTR_API void upper(const u32s* src, size_t len, u32s* dest);
|
||
static SIMSTR_API void lower(const u32s* src, size_t len, u32s* dest);
|
||
|
||
static SIMSTR_API int compareiu(const u32s* text1, size_t len1, const u32s* text2, size_t len2);
|
||
static SIMSTR_API size_t hashia(const u32s* src, size_t s);
|
||
static SIMSTR_API size_t hashiu(const u32s* src, size_t s);
|
||
};
|
||
|
||
template<>
|
||
struct unicode_traits<wchar_t> {
|
||
static void upper(const wchar_t* src, size_t len, wchar_t* dest) {
|
||
unicode_traits<wchar_type>::upper(to_w(src), len, to_w(dest));
|
||
}
|
||
static void lower(const wchar_t* src, size_t len, wchar_t* dest) {
|
||
unicode_traits<wchar_type>::lower(to_w(src), len, to_w(dest));
|
||
}
|
||
|
||
static int compareiu(const wchar_t* text1, size_t len1, const wchar_t* text2, size_t len2) {
|
||
return unicode_traits<wchar_type>::compareiu(to_w(text1), len1, to_w(text2), len2);
|
||
}
|
||
static size_t hashia(const wchar_t* src, size_t s) {
|
||
return unicode_traits<wchar_type>::hashia(to_w(src), s);
|
||
}
|
||
static size_t hashiu(const wchar_t* src, size_t s) {
|
||
return unicode_traits<wchar_type>::hashiu(to_w(src), s);
|
||
}
|
||
};
|
||
|
||
namespace str {
|
||
constexpr const size_t npos = static_cast<size_t>(-1); //NOLINT
|
||
} // namespace str
|
||
|
||
template<typename K>
|
||
struct ch_traits : std::char_traits<K>{};
|
||
|
||
template<size_t N>
|
||
concept is_const_pattern = N > 1 && N <= 17;
|
||
|
||
template<typename K, size_t I>
|
||
struct _ascii_mask { // NOLINT
|
||
constexpr static const size_t value = size_t(K(~0x7F)) << ((I - 1) * sizeof(K) * 8) | _ascii_mask<K, I - 1>::value;
|
||
};
|
||
|
||
template<typename K>
|
||
struct _ascii_mask<K, 0> {
|
||
constexpr static const size_t value = 0;
|
||
};
|
||
|
||
template<typename K>
|
||
struct ascii_mask { // NOLINT
|
||
using uns = std::make_unsigned_t<K>;
|
||
constexpr static const size_t WIDTH = sizeof(size_t) / sizeof(uns);
|
||
constexpr static const size_t VALUE = _ascii_mask<uns, WIDTH>::value;
|
||
};
|
||
|
||
template<typename K>
|
||
constexpr inline bool isAsciiUpper(K k) {
|
||
return k >= 'A' && k <= 'Z';
|
||
}
|
||
|
||
template<typename K>
|
||
constexpr inline bool isAsciiLower(K k) {
|
||
return k >= 'a' && k <= 'z';
|
||
}
|
||
|
||
template<typename K>
|
||
constexpr inline K makeAsciiLower(K k) {
|
||
return isAsciiUpper(k) ? k | 0x20 : k;
|
||
}
|
||
|
||
template<typename K>
|
||
constexpr inline K makeAsciiUpper(K k) {
|
||
return isAsciiLower(k) ? k & ~0x20 : k;
|
||
}
|
||
|
||
enum TrimSides { TrimLeft = 1, TrimRight = 2, TrimAll = 3 };
|
||
template<TrimSides S, typename K, size_t N, bool withSpaces = false>
|
||
struct trim_operator;
|
||
|
||
template<typename K, size_t N, size_t L>
|
||
struct expr_replaces;
|
||
|
||
template<typename T>
|
||
concept FromIntNumber =
|
||
is_one_of_type<std::remove_cv_t<T>, unsigned char, int, short, long, long long, unsigned, unsigned short, unsigned long, unsigned long long>::value;
|
||
|
||
template<typename T>
|
||
concept ToIntNumber = FromIntNumber<T> || is_one_of_type<T, int8_t>::value;
|
||
|
||
#if defined(_MSC_VER) && _MSC_VER <= 1933
|
||
template<typename K, typename... Args>
|
||
using FmtString = std::_Basic_format_string<K, std::type_identity_t<Args>...>;
|
||
#elif __clang_major__ >= 15 || _MSC_VER > 1933 || __GNUC__ >= 13
|
||
template<typename K, typename... Args>
|
||
using FmtString = std::basic_format_string<K, std::type_identity_t<Args>...>;
|
||
#else
|
||
template<typename K, typename... Args>
|
||
using FmtString = std::basic_string_view<K>;
|
||
#endif
|
||
|
||
template<typename K, bool I, typename T>
|
||
struct need_sign { // NOLINT
|
||
bool sign;
|
||
need_sign(T& t) : sign(t < 0) {
|
||
if (sign && t != std::numeric_limits<T>::min())
|
||
t = -t;
|
||
}
|
||
void after(K*& ptr) {
|
||
if (sign)
|
||
*--ptr = '-';
|
||
}
|
||
};
|
||
|
||
template<typename K, typename T>
|
||
struct need_sign<K, false, T> {
|
||
need_sign(T&) {}
|
||
void after(K*&) {}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Перечисление с возможными результатами преобразования строки в целое число
|
||
* @en @brief Enumeration with possible results of converting a string to an integer
|
||
*/
|
||
enum class IntConvertResult : char {
|
||
Success, //!< Успешно
|
||
BadSymbolAtTail, //!< Число закончилось не числовым символом
|
||
Overflow, //!< Переполнение, число не помещается в заданный тип
|
||
NotNumber //!< Вообще не число
|
||
};
|
||
|
||
template<bool CanNegate, bool CheckOverflow, typename T>
|
||
struct result_type_selector { // NOLINT
|
||
using type = T;
|
||
};
|
||
|
||
template<typename T>
|
||
struct result_type_selector<true, false, T> {
|
||
using type = std::make_unsigned_t<T>;
|
||
};
|
||
|
||
template<unsigned Base>
|
||
constexpr unsigned digit_width() {
|
||
if (Base <=2) {
|
||
return 1;
|
||
}
|
||
if (Base <= 4) {
|
||
return 2;
|
||
}
|
||
if (Base <= 8) {
|
||
return 3;
|
||
}
|
||
if (Base <= 16) {
|
||
return 4;
|
||
}
|
||
if (Base <= 32) {
|
||
return 5;
|
||
}
|
||
return 6;
|
||
}
|
||
|
||
template<typename T, unsigned Base>
|
||
constexpr unsigned max_overflow_digits = (sizeof(T) * CHAR_BIT) / digit_width<Base>();
|
||
|
||
struct int_convert { // NOLINT
|
||
private:
|
||
inline static const uint8_t NUMBERS[] = {
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3,
|
||
4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16,
|
||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};
|
||
|
||
template<typename K, unsigned Base>
|
||
static uint8_t toDigit(K s) {
|
||
auto us = static_cast<std::make_unsigned_t<K>>(s);
|
||
if constexpr (Base <= 10) {
|
||
return us - '0';
|
||
} else {
|
||
if constexpr (sizeof(K) == 1) {
|
||
return NUMBERS[us];
|
||
} else {
|
||
return us < 256 ? NUMBERS[us] : 255;
|
||
}
|
||
}
|
||
}
|
||
|
||
template<typename K, ToIntNumber T, unsigned Base, bool CheckOverflow>
|
||
requires(Base != 0)
|
||
static std::tuple<T, IntConvertResult, size_t> parse(const K* start, const K* current, const K* end, bool negate) {
|
||
using u_type = std::make_unsigned_t<T>;
|
||
#ifndef HAS_BUILTIN_OVERFLOW
|
||
u_type maxMult = 0, maxAdd = 0;
|
||
if constexpr (CheckOverflow) {
|
||
maxMult = std::numeric_limits<u_type>::max() / Base;
|
||
maxAdd = std::numeric_limits<u_type>::max() % Base;
|
||
}
|
||
#endif
|
||
u_type number = 0;
|
||
unsigned maxDigits = max_overflow_digits<u_type, Base>;
|
||
IntConvertResult error = IntConvertResult::NotNumber;
|
||
const K* from = current;
|
||
|
||
bool no_need_check_o_f = !CheckOverflow || end - current <= maxDigits;
|
||
|
||
if (no_need_check_o_f) {
|
||
for (;;) {
|
||
const unsigned char digit = toDigit<K, Base>(*current);
|
||
if (digit >= Base) {
|
||
break;
|
||
}
|
||
number = number * Base + digit;
|
||
if (++current == end) {
|
||
error = IntConvertResult::Success;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
for (;maxDigits; maxDigits--) {
|
||
const unsigned char digit = toDigit<K, Base>(*current);
|
||
if (digit >= Base) {
|
||
break;
|
||
}
|
||
number = number * Base + digit;
|
||
++current;
|
||
}
|
||
if (!maxDigits) {
|
||
// Прошли все цифры, дальше надо с проверкой на overflow
|
||
// All numbers have passed, then we need to check for overflow
|
||
for (;;) {
|
||
const unsigned char digit = toDigit<K, Base>(*current);
|
||
if (digit >= Base) {
|
||
break;
|
||
}
|
||
#ifdef HAS_BUILTIN_OVERFLOW
|
||
if (__builtin_mul_overflow(number, Base, &number) ||
|
||
__builtin_add_overflow(number, digit, &number)) {
|
||
#else
|
||
if (number < maxMult || (number == maxMult && number < maxAdd)) {
|
||
number = number * Base + digit;
|
||
} else {
|
||
#endif
|
||
error = IntConvertResult::Overflow;
|
||
while(++current < end) {
|
||
if (toDigit<K, Base>(*current) >= Base) {
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
if (++current == end) {
|
||
error = IntConvertResult::Success;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
T result;
|
||
if constexpr (std::is_signed_v<T>) {
|
||
result = negate ? 0 - number : number;
|
||
if constexpr (CheckOverflow) {
|
||
if (error != IntConvertResult::Overflow) {
|
||
if (number > std::numeric_limits<T>::max() + (negate ? 1 : 0)) {
|
||
error = IntConvertResult::Overflow;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
result = number;
|
||
}
|
||
if (error == IntConvertResult::NotNumber && current > from) {
|
||
error = IntConvertResult::BadSymbolAtTail;
|
||
}
|
||
return {result, error, current - start};
|
||
}
|
||
public:
|
||
// Если Base = 0 - то пытается определить основание по префиксу 0[xX] как 16, 0 как 8, иначе 10
|
||
// Если Base = -1 - то пытается определить основание по префиксу 0[xX] как 16, 0[bB] как 2, 0[oO] или 0 как 8, иначе 10
|
||
// If Base = 0, then it tries to determine the base by the prefix 0[xX] as 16, 0 as 8, otherwise 10
|
||
// If Base = -1 - then tries to determine the base by the prefix 0[xX] as 16, 0[bB] as 2, 0[oO] or 0 as 8, otherwise 10
|
||
template<typename K, ToIntNumber T, unsigned Base = 0, bool CheckOverflow = true, bool SkipWs = true, bool AllowSign = true>
|
||
requires(Base == -1 || (Base < 37 && Base != 1))
|
||
static std::tuple<T, IntConvertResult, size_t> to_integer(const K* start, size_t len) noexcept {
|
||
const K *ptr = start, *end = ptr + len;
|
||
bool negate = false;
|
||
if constexpr (SkipWs) {
|
||
while (ptr < end && std::make_unsigned_t<K>(*ptr) <= ' ')
|
||
ptr++;
|
||
}
|
||
if (ptr != end) {
|
||
if constexpr (std::is_signed_v<T>) {
|
||
if constexpr (AllowSign) {
|
||
// Может быть число, +число или -число
|
||
// Can be a number, +number or -number
|
||
if (*ptr == '+') {
|
||
ptr++;
|
||
} else if (*ptr == '-') {
|
||
negate = true;
|
||
ptr++;
|
||
}
|
||
} else {
|
||
// Может быть число или -число
|
||
// Can be a number or -number
|
||
if (*ptr == '-') {
|
||
negate = true;
|
||
ptr++;
|
||
}
|
||
}
|
||
} else if constexpr (AllowSign) {
|
||
// Может быть число или +число
|
||
// Can be a number or +number
|
||
if (*ptr == '+') {
|
||
ptr++;
|
||
}
|
||
}
|
||
}
|
||
if (ptr != end) {
|
||
if constexpr (Base == 0 || Base == -1) {
|
||
if (*ptr == '0') {
|
||
ptr++;
|
||
if (ptr != end) {
|
||
if (*ptr == 'x' || *ptr == 'X') {
|
||
return parse<K, T, 16, CheckOverflow>(start, ++ptr, end, negate);
|
||
}
|
||
if constexpr (Base == -1) {
|
||
if (*ptr == 'b' || *ptr == 'B') {
|
||
return parse<K, T, 2, CheckOverflow>(start, ++ptr, end, negate);
|
||
}
|
||
if (*ptr == 'o' || *ptr == 'O') {
|
||
return parse<K, T, 8, CheckOverflow>(start, ++ptr, end, negate);
|
||
}
|
||
}
|
||
return parse<K, T, 8, CheckOverflow>(start, --ptr, end, negate);
|
||
}
|
||
return {0, IntConvertResult::Success, ptr - start};
|
||
}
|
||
return parse<K, T, 10, CheckOverflow>(start, ptr, end, negate);
|
||
} else
|
||
return parse<K, T, Base, CheckOverflow>(start, ptr, end, negate);
|
||
}
|
||
return {0, IntConvertResult::NotNumber, ptr - start};
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
class Splitter;
|
||
|
||
template<typename K, typename Impl, bool Mutable> class buffer_pointers;
|
||
|
||
/*!
|
||
* @ru @brief Базовый класс для строкового буфера.
|
||
* @tparam K - тип символов.
|
||
* @tparam Impl - класс реализации.
|
||
* @en @brief Base class for a string buffer.
|
||
* @tparam K - character type.
|
||
* @tparam Impl - implementation class.
|
||
*/
|
||
template<typename K, typename Impl>
|
||
class buffer_pointers<K, Impl, false> {
|
||
const Impl& d() const { return *static_cast<const Impl*>(this); }
|
||
public:
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер символов строки
|
||
* @return const K* - указатель на константный буфер символов строки
|
||
* @en @brief Get a pointer to a constant character buffer of a string
|
||
* @return const K* - pointer to a constant string character buffer
|
||
*/
|
||
const K* c_str() const { return d().symbols(); }
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер символов строки.
|
||
* @return const K* - указатель на константный буфер символов строки.
|
||
* @en @brief Get a pointer to a constant character buffer of a string.
|
||
* @return const K* - pointer to a constant buffer of string characters.
|
||
*/
|
||
const K* data() const { return d().symbols(); }
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер символов строки.
|
||
* @return const K* - указатель на константный буфер символов строки.
|
||
* @en @brief Get a pointer to a constant character buffer of a string.
|
||
* @return const K* - pointer to a constant buffer of string characters.
|
||
*/
|
||
const K* begin() const { return d().symbols(); }
|
||
/*!
|
||
* @ru @brief Указатель на константный символ после после последнего символа строки.
|
||
* @return const K* - конец строки.
|
||
* @en @brief Pointer to a constant character after the last character of the string.
|
||
* @return const K* - end of line.
|
||
*/
|
||
const K* end() const { return d().symbols() + d().length(); }
|
||
};
|
||
|
||
template<typename K, typename Impl>
|
||
class buffer_pointers<K, Impl, true> : public buffer_pointers<K, Impl, false> {
|
||
Impl& d() { return *static_cast<Impl*>(this); }
|
||
using base = buffer_pointers<K, Impl, false>;
|
||
public:
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер символов строки.
|
||
* @return const K* - указатель на константный буфер символов строки.
|
||
* @en @brief Get a pointer to a constant character buffer of a string.
|
||
* @return const K* - pointer to a constant buffer of string characters.
|
||
*/
|
||
const K* data() const { return base::data(); }
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер символов строки.
|
||
* @return const K* - указатель на константный буфер символов строки.
|
||
* @en @brief Get a pointer to a constant character buffer of a string.
|
||
* @return const K* - pointer to a constant buffer of string characters.
|
||
*/
|
||
const K* begin() const { return base::begin(); }
|
||
/*!
|
||
* @ru @brief Указатель на константный символ после после последнего символа строки.
|
||
* @return const K* - конец строки.
|
||
* @en @brief Pointer to a constant character after the last character of the string.
|
||
* @return const K* - end of line.
|
||
*/
|
||
const K* end() const { return base::end(); }
|
||
/*!
|
||
* @ru @brief Получить указатель на буфер символов строки.
|
||
* @return K* - указатель на буфер символов строки.
|
||
* @en @brief Get a pointer to the string's character buffer.
|
||
* @return K* - pointer to a string character buffer.
|
||
*/
|
||
K* data() { return d().str(); }
|
||
/*!
|
||
* @ru @brief Получить указатель на буфер символов строки.
|
||
* @return K* - указатель на буфер символов строки.
|
||
* @en @brief Get a pointer to the string's character buffer.
|
||
* @return K* - pointer to a string character buffer.
|
||
*/
|
||
K* begin() { return d().str(); }
|
||
/*!
|
||
* @ru @brief Указатель на символ после после последнего символа строки.
|
||
* @return K* - конец строки.
|
||
* @en @brief Pointer to the character after the last character of the string.
|
||
* @return K* - end of line.
|
||
*/
|
||
K* end() { return d().str() + d().length(); }
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Класс с базовыми константными строковыми алгоритмами.
|
||
* @details Является базой для классов, могущих выполнять константные операции со строками.
|
||
* Ничего не знает о хранении строк, ни сам, ни у класса наследника, то есть работает
|
||
* только с указателем на строку и её длиной.
|
||
* Для работы класс-наследник должен реализовать методы:
|
||
* - size_t length() const noexcept - возвращает длину строки.
|
||
* - const K* symbols() const noexcept - возвращает указатель на начало строки.
|
||
* - bool is_empty() const noexcept - проверка, не пустая ли строка.
|
||
* @tparam K - тип символов.
|
||
* @tparam StrRef - тип хранилища куска строки.
|
||
* @tparam Impl - конечный класс наследник.
|
||
* @en @brief A class with basic constant string algorithms.
|
||
* @details Is the base for classes that can perform constant operations on strings.
|
||
* Doesn’t know anything about storing strings, neither itself nor the descendant class, that is, it works
|
||
* only with a pointer to a string and its length.
|
||
* To work, the descendant class must implement the following methods:
|
||
* - size_t length() const noexcept - returns the length of the string.
|
||
* - const K* symbols() const noexcept - returns a pointer to the beginning of the line.
|
||
* - bool is_empty() const noexcept - checks whether the string is empty.
|
||
* @tparam K - character type.
|
||
* @tparam StrRef - storage type for the string chunk.
|
||
* @tparam Impl - the final class is the successor.
|
||
*/
|
||
template<typename K, typename StrRef, typename Impl, bool Mutable>
|
||
class str_algs : public buffer_pointers<K, Impl, Mutable> {
|
||
const Impl& d() const noexcept {
|
||
return *static_cast<const Impl*>(this);
|
||
}
|
||
size_t _len() const noexcept {
|
||
return d().length();
|
||
}
|
||
const K* _str() const noexcept {
|
||
return d().symbols();
|
||
}
|
||
bool _is_empty() const noexcept {
|
||
return d().is_empty();
|
||
}
|
||
|
||
public:
|
||
using symb_type = K;
|
||
using str_piece = StrRef;
|
||
using traits = ch_traits<K>;
|
||
using uni = unicode_traits<K>;
|
||
using uns_type = std::make_unsigned_t<K>;
|
||
using my_type = Impl;
|
||
using base = str_algs<K, StrRef, Impl, Mutable>;
|
||
str_algs() = default;
|
||
|
||
/*!
|
||
* @ru @brief Копировать строку в указанный буфер.
|
||
* @details Метод предполагает, что размер выделенного буфера достаточен для всей строки, т.е.
|
||
* предварительно была запрошена `length()`. Не добавляет `\0`.
|
||
* @param ptr - указатель на буфер.
|
||
* @return указатель на символ после конца размещённой в буфере строки.
|
||
* @en @brief Copy the string to the specified buffer.
|
||
* @details The method assumes that the size of the allocated buffer is sufficient for the entire line, i.e.
|
||
* `length()` was previously requested. Does not add `\0`.
|
||
* @param ptr - pointer to the buffer.
|
||
* @return pointer to the character after the end of the symbols placed in the buffer.
|
||
*/
|
||
constexpr K* place(K* ptr) const noexcept {
|
||
size_t myLen = _len();
|
||
if (myLen) {
|
||
traits::copy(ptr, _str(), myLen);
|
||
return ptr + myLen;
|
||
}
|
||
return ptr;
|
||
}
|
||
/*!
|
||
* @ru @brief Копировать строку в указанный буфер.
|
||
* @details Метод добавляет `\0` после скопированных символов. Не выходит за границы буфера.
|
||
* @param buffer - указатель на буфер
|
||
* @param bufSize - размер буфера в символах.
|
||
* @en @brief Copy the string to the specified buffer.
|
||
* @details The method adds `\0` after the copied characters. Does not exceed buffer boundaries.
|
||
* @param buffer - pointer to buffer
|
||
* @param bufSize - buffer size in characters.
|
||
*/
|
||
void copy_to(K* buffer, size_t bufSize) {
|
||
size_t tlen = std::min(_len(), bufSize - 1);
|
||
if (tlen)
|
||
traits::copy(buffer, _str(), tlen);
|
||
buffer[tlen] = 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Размер строки в символах.
|
||
* @return size_t
|
||
* @en @brief The size of the string in characters.
|
||
* @return size_t
|
||
*/
|
||
size_t size() const {
|
||
return _len();
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Преобразовать себя в "кусок строки", включающий всю строку.
|
||
* @return str_piece.
|
||
* @en @brief Convert itself to a "string chunk" that includes the entire string.
|
||
* @return str_piece.
|
||
*/
|
||
constexpr operator str_piece() const noexcept {
|
||
return str_piece{_str(), _len()};
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать себя в "кусок строки", включающий всю строку.
|
||
* @return str_piece.
|
||
* @en @brief Convert itself to a "string chunk" that includes the entire string.
|
||
* @return str_piece.
|
||
*/
|
||
str_piece to_str() const noexcept {
|
||
return {_str(), _len()};
|
||
}
|
||
/*!
|
||
* @ru @brief Конвертировать в std::string_view.
|
||
* @return std::string_view.
|
||
* @en @brief Convert to std::string_view.
|
||
* @return std::string_view.
|
||
*/
|
||
std::string_view to_sv() const noexcept {
|
||
return {_str(), _len()};
|
||
}
|
||
/*!
|
||
* @ru @brief Конвертировать в std::string.
|
||
* @return std::string.
|
||
* @en @brief Convert to std::string.
|
||
* @return std::string.
|
||
*/
|
||
std::string to_string() const noexcept {
|
||
return {_str(), _len()};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить часть строки как "simple_str".
|
||
* @param from - количество символов от начала строки.
|
||
* @param len - количество символов в получаемом "куске".
|
||
* @return Подстроку, simple_str.
|
||
* @details Если `from` меньше нуля, то отсчитывается `-from` символов от конца строки в сторону начала.
|
||
* Если `len` меньше или равно нулю, то отсчитать `-len` символов от конца строки
|
||
* @en @brief Get part of a string as "simple_str".
|
||
* @param from - number of characters from the beginning of the line.
|
||
* @param len - the number of characters in the resulting "chunk".
|
||
* @return Substring, simple_str.
|
||
* @details If `from` is less than zero, then `-from` characters are counted from the end of the line towards the beginning.
|
||
* If `len` is less than or equal to zero, then count `-len` characters from the end of the line
|
||
* @~
|
||
* ```cpp
|
||
* "0123456789"_ss(5, 2) == "56";
|
||
* "0123456789"_ss(5) == "56789";
|
||
* "0123456789"_ss(5, -1) == "5678";
|
||
* "0123456789"_ss(-3) == "789";
|
||
* "0123456789"_ss(-3, 2) == "78";
|
||
* "0123456789"_ss(-4, -1) == "678";
|
||
* ```
|
||
*/
|
||
constexpr str_piece operator()(ptrdiff_t from, ptrdiff_t len = 0) const noexcept {
|
||
size_t myLen = _len(), idxStart = from >= 0 ? from : myLen > -from ? myLen + from : 0,
|
||
idxEnd = len > 0 ? idxStart + len : myLen > -len ? myLen + len : 0;
|
||
if (idxEnd > myLen)
|
||
idxEnd = myLen;
|
||
if (idxStart > idxEnd)
|
||
idxStart = idxEnd;
|
||
return str_piece{_str() + idxStart, idxEnd - idxStart};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить часть строки как "кусок строки".
|
||
* @param from - количество символов от начала строки. При превышении размера строки вернёт пустую строку.
|
||
* @param len - количество символов в получаемом "куске". При выходе за пределы строки вернёт всё до конца строки.
|
||
* @return Подстроку, simple_str.
|
||
* @en @brief Get part of a string as "string chunk".
|
||
* @param from - number of characters from the beginning of the line. If the string size is exceeded, it will return an empty string.
|
||
* @param len - the number of characters in the resulting "chunk". When going beyond the line, it will return everything up to the end of the line.
|
||
* @return Substring, simple_str.
|
||
*/
|
||
constexpr str_piece mid(size_t from, size_t len = -1) const noexcept {
|
||
size_t myLen = _len(), idxStart = from, idxEnd = from > std::numeric_limits<size_t>::max() - len ? myLen : from + len;
|
||
if (idxEnd > myLen)
|
||
idxEnd = myLen;
|
||
if (idxStart > idxEnd)
|
||
idxStart = idxEnd;
|
||
return str_piece{_str() + idxStart, idxEnd - idxStart};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить подстроку simple_str с позиции от from до позиции to (не включая её).
|
||
* @details Для производительности метод никак не проверяет выходы за границы строки, используйте
|
||
* в сценариях, когда точно знаете, что это позиции внутри строки и to >= from.
|
||
* @param from - начальная позиция.
|
||
* @param to - конечная позиция (не входит в результат).
|
||
* @return Подстроку, simple_str.
|
||
* @en @brief Get the substring simple_str from position from to position to (not including it).
|
||
* @details For performance reasons, the method does not check for line boundaries in any way, use
|
||
* in scenarios when you know for sure that these are positions inside the line and to >= from.
|
||
* @param from - starting position.
|
||
* @param to - final position (not included in the result).
|
||
* @return Substring, simple_str.
|
||
*/
|
||
constexpr str_piece from_to(size_t from, size_t to) const noexcept {
|
||
return str_piece{_str() + from, to - from};
|
||
}
|
||
/*!
|
||
* @ru @brief Проверка на пустоту.
|
||
* @en @brief Check for emptiness.
|
||
*/
|
||
bool operator!() const noexcept {
|
||
return _is_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Получить символ на заданной позиции .
|
||
* @param idx - индекс символа. Для отрицательных значений отсчитывается от конца строки.
|
||
* @return K - символ.
|
||
* @details Не производит проверку на выход за границы строки.
|
||
* @en @brief Get the character at the given position.
|
||
* @param idx - symbol index. For negative values, it is counted from the end of the line.
|
||
* @return K - character.
|
||
* @details Does not check for line boundaries.
|
||
*/
|
||
K at(ptrdiff_t idx) const {
|
||
return _str()[idx >= 0 ? idx : _len() + idx];
|
||
}
|
||
// Сравнение строк
|
||
// String comparison
|
||
constexpr int compare(const K* text, size_t len) const {
|
||
size_t myLen = _len();
|
||
int cmp = traits::compare(_str(), text, std::min(myLen, len));
|
||
return cmp == 0 ? (myLen > len ? 1 : myLen == len ? 0 : -1) : cmp;
|
||
}
|
||
/*!
|
||
* @ru @brief Сравнение строк посимвольно.
|
||
* @param o - другая строка.
|
||
* @return <0 эта строка меньше, ==0 - строки равны, >0 - эта строка больше.
|
||
* @en @brief Compare strings character by character.
|
||
* @param o - another line.
|
||
* @return <0 this string is less, ==0 - strings are equal, >0 - this string is greater.
|
||
*/
|
||
constexpr int compare(str_piece o) const {
|
||
return compare(o.symbols(), o.length());
|
||
}
|
||
/*!
|
||
* @ru @brief Сравнение с C-строкой посимвольно.
|
||
* @param text - другая строка.
|
||
* @return <0 эта строка меньше, ==0 - строки равны, >0 - эта строка больше.
|
||
* @en @brief Compare with C-string character by character.
|
||
* @param text - another line.
|
||
* @return <0 this string is less, ==0 - strings are equal, >0 - this string is greater.
|
||
*/
|
||
constexpr int strcmp(const K* text) const {
|
||
size_t myLen = _len(), idx = 0;
|
||
const K* ptr = _str();
|
||
for (; idx < myLen; idx++) {
|
||
uns_type s1 = (uns_type)text[idx];
|
||
if (!s1) {
|
||
return 1;
|
||
}
|
||
uns_type s2 = (uns_type)ptr[idx];
|
||
if (s1 < s2) {
|
||
return 1;
|
||
} else if (s1 > s2) {
|
||
return -1;
|
||
}
|
||
}
|
||
return text[idx] == 0 ? 0 : -1;
|
||
}
|
||
|
||
constexpr bool equal(const K* text, size_t len) const noexcept {
|
||
return len == _len() && traits::compare(_str(), text, len) == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Сравнение строк на равенство.
|
||
* @param other - другая строка.
|
||
* @return равны ли строки.
|
||
* @en @brief String comparison for equality.
|
||
* @param other - another line.
|
||
* @return whether the strings are equal.
|
||
*/
|
||
constexpr bool equal(str_piece other) const noexcept {
|
||
return equal(other.symbols(), other.length());
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор сравнение строк на равенство.
|
||
* @param other - другая строка.
|
||
* @return равны ли строки.
|
||
* @en @brief Operator comparing strings for equality.
|
||
* @param other - another line.
|
||
* @return whether the strings are equal.
|
||
*/
|
||
constexpr bool operator==(const base& other) const noexcept {
|
||
return equal(other._str(), other._len());
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор сравнения строк.
|
||
* @param other - другая строка.
|
||
* @en @brief String comparison operator.
|
||
* @param other - another line.
|
||
*/
|
||
constexpr auto operator<=>(const base& other) const noexcept {
|
||
return compare(other._str(), other._len()) <=> 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор сравнения строки и строкового литерала на равенство.
|
||
* @param other - строковый литерал.
|
||
* @en @brief Operator for comparing a string and a string literal for equality.
|
||
* @param other - string literal.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
bool operator==(T&& other) const noexcept {
|
||
return N - 1 == _len() && traits::compare(_str(), other, N - 1) == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор сравнения строки и строкового литерала.
|
||
* @param other - строковый литерал.
|
||
* @en @brief Comparison operator between a string and a string literal.
|
||
* @param other is a string literal.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
auto operator<=>(T&& other) const noexcept {
|
||
size_t myLen = _len();
|
||
int cmp = traits::compare(_str(), other, std::min(myLen, N - 1));
|
||
int res = cmp == 0 ? (myLen > N - 1 ? 1 : myLen == N - 1 ? 0 : -1) : cmp;
|
||
return res <=> 0;
|
||
}
|
||
|
||
// Сравнение ascii строк без учёта регистра
|
||
// Compare ascii strings without taking into account case
|
||
int compare_ia(const K* text, size_t len) const noexcept { // NOLINT
|
||
if (!len)
|
||
return _is_empty() ? 0 : 1;
|
||
size_t myLen = _len(), checkLen = std::min(myLen, len);
|
||
const uns_type *ptr1 = reinterpret_cast<const uns_type*>(_str()), *ptr2 = reinterpret_cast<const uns_type*>(text);
|
||
while (checkLen--) {
|
||
uns_type s1 = *ptr1++, s2 = *ptr2++;
|
||
if (s1 == s2)
|
||
continue;
|
||
s1 = makeAsciiLower(s1);
|
||
s2 = makeAsciiLower(s2);
|
||
if (s1 > s2)
|
||
return 1;
|
||
else if (s1 < s2)
|
||
return -1;
|
||
}
|
||
return myLen == len ? 0 : myLen > len ? 1 : -1;
|
||
}
|
||
/*!
|
||
* @ru @brief Сравнение строк посимвольно без учёта регистра ASCII символов.
|
||
* @param text - другая строка.
|
||
* @return <0 эта строка меньше, ==0 - строки равны, >0 - эта строка больше.
|
||
* @en @brief Compare strings character by character and not case sensitive ASCII characters.
|
||
* @param text - another line.
|
||
* @return <0 this string is less, ==0 - strings are equal, >0 - this string is greater.
|
||
*/
|
||
int compare_ia(str_piece text) const noexcept { // NOLINT
|
||
return compare_ia(text.symbols(), text.length());
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Равна ли строка другой строке посимвольно без учёта регистра ASCII символов.
|
||
* @param text - другая строка.
|
||
* @return равны ли строки.
|
||
* @en @brief Whether a string is equal to another string, character-by-character-insensitive, of ASCII characters.
|
||
* @param text - another line.
|
||
* @return whether the strings are equal.
|
||
*/
|
||
bool equal_ia(str_piece text) const noexcept { // NOLINT
|
||
return text.length() == _len() && compare_ia(text.symbols(), text.length()) == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Меньше ли строка другой строки посимвольно без учёта регистра ASCII символов.
|
||
* @param text - другая строка.
|
||
* @return меньше ли строка.
|
||
* @en @brief Whether a string is smaller than another string, character-by-character-insensitive, ASCII characters.
|
||
* @param text - another line.
|
||
* @return whether the string is smaller.
|
||
*/
|
||
bool less_ia(str_piece text) const noexcept { // NOLINT
|
||
return compare_ia(text.symbols(), text.length()) < 0;
|
||
}
|
||
|
||
int compare_iu(const K* text, size_t len) const noexcept { // NOLINT
|
||
if (!len)
|
||
return _is_empty() ? 0 : 1;
|
||
return uni::compareiu(_str(), _len(), text, len);
|
||
}
|
||
/*!
|
||
* @ru @brief Сравнение строк посимвольно без учёта регистра Unicode символов первой плоскости (<0xFFFF).
|
||
* @param text - другая строка.
|
||
* @return <0 эта строка меньше, ==0 - строки равны, >0 - эта строка больше.
|
||
* @en @brief Compare strings character by character without taking into account the case of Unicode characters of the first plane (<0xFFFF).
|
||
* @param text - another line.
|
||
* @return <0 this string is less, ==0 - strings are equal, >0 - this string is greater.
|
||
*/
|
||
int compare_iu(str_piece text) const noexcept { // NOLINT
|
||
return compare_iu(text.symbols(), text.length());
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Равна ли строка другой строке посимвольно без учёта регистра Unicode символов первой плоскости (<0xFFFF).
|
||
* @param text - другая строка.
|
||
* @return равны ли строки.
|
||
* @en @brief Whether a string is equal to another string, character-by-character-insensitive, of the Unicode characters of the first plane (<0xFFFF).
|
||
* @param text - another line.
|
||
* @return whether the strings are equal.
|
||
*/
|
||
bool equal_iu(str_piece text) const noexcept { // NOLINT
|
||
return text.length() == _len() && compare_iu(text.symbols(), text.length()) == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Меньше ли строка другой строки посимвольно без учёта регистра Unicode символов первой плоскости (<0xFFFF).
|
||
* @param text - другая строка.
|
||
* @return меньше ли строка.
|
||
* @en @brief Whether a string is smaller than another string, character-by-character-insensitive, of the Unicode characters of the first plane (<0xFFFF).
|
||
* @param text - another line.
|
||
* @return whether the string is smaller.
|
||
*/
|
||
bool less_iu(str_piece text) const noexcept { // NOLINT
|
||
return compare_iu(text.symbols(), text.length()) < 0;
|
||
}
|
||
|
||
size_t find(const K* pattern, size_t lenPattern, size_t offset) const noexcept {
|
||
size_t lenText = _len();
|
||
// Образец, не вмещающийся в строку и пустой образец не находим
|
||
// We don't look for an empty line or a line longer than the text.
|
||
if (!lenPattern || offset >= lenText || offset + lenPattern > lenText)
|
||
return str::npos;
|
||
lenPattern--;
|
||
const K *text = _str(), *last = text + lenText - lenPattern, first = pattern[0];
|
||
pattern++;
|
||
for (const K* fnd = text + offset;; ++fnd) {
|
||
fnd = traits::find(fnd, last - fnd, first);
|
||
if (!fnd)
|
||
return str::npos;
|
||
if (traits::compare(fnd + 1, pattern, lenPattern) == 0)
|
||
return static_cast<size_t>(fnd - text);
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Найти начало первого вхождения подстроки в этой строке.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию начала вхождения подстроки, или -1, если не найдена.
|
||
* @en @brief Find the beginning of the first occurrence of a substring in this string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - the position of the beginning of the occurrence of the substring, or -1 if not found.
|
||
*/
|
||
size_t find(str_piece pattern, size_t offset = 0) const noexcept {
|
||
return find(pattern.symbols(), pattern.length(), offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти начало первого вхождения подстроки в этой строке или выкинуть исключение.
|
||
* @tparam Exc - тип исключения.
|
||
* @tparam Args... - типы параметров для конструирования исключения, выводятся из аргументов.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @param args - аргументы для конструктора исключения.
|
||
* @return size_t - позицию начала вхождения подстроки, или выбрасывает исключение Exc, если не найдена.
|
||
* @en @brief Find the beginning of the first occurrence of a substring in this string or throw an exception.
|
||
* @tparam Exc - exception type.
|
||
* @tparam Args... - types of parameters for constructing an exception, inferred from the arguments.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @param args - arguments for the exception constructor.
|
||
* @return size_t - the position of the beginning of the substring occurrence, or throws an Exc exception if not found.
|
||
*/
|
||
template<typename Exc, typename ... Args> requires std::is_constructible_v<Exc, Args...>
|
||
size_t find_or_throw(str_piece pattern, size_t offset = 0, Args&& ... args) const noexcept {
|
||
if (auto fnd = find(pattern.symbols(), pattern.length(), offset); fnd != str::npos) {
|
||
return fnd;
|
||
}
|
||
throw Exc(std::forward<Args>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти конец вхождения подстроки в этой строке.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию сразу за вхождением подстроки, или -1, если не найдена.
|
||
* @en @brief Find the end of the occurrence of a substring in this string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - the position immediately after the occurrence of the substring, or -1 if not found.
|
||
*/
|
||
size_t find_end(str_piece pattern, size_t offset = 0) const noexcept {
|
||
size_t fnd = find(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? fnd : fnd + pattern.length();
|
||
}
|
||
/*!
|
||
* @ru @brief Найти начало первого вхождения подстроки в этой строке или конец строки.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию начала вхождения подстроки, или длину строки, если не найдена.
|
||
* @en @brief Find the beginning of the first occurrence of a substring in this string or the end of the string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - the position at which the substring begins, or the length of the string if not found.
|
||
*/
|
||
size_t find_or_all(str_piece pattern, size_t offset = 0) const noexcept {
|
||
auto fnd = find(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? _len() : fnd;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти конец первого вхождения подстроки в этой строке или конец строки.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию сразу за вхождением подстроки, или длину строки, если не найдена.
|
||
* @en @brief Find the end of the first occurrence of a substring in this string, or the end of a string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - the position immediately after the occurrence of the substring, or the length of the string if not found.
|
||
*/
|
||
size_t find_end_or_all(str_piece pattern, size_t offset = 0) const noexcept {
|
||
auto fnd = find(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? _len() : fnd + pattern.length();
|
||
}
|
||
|
||
size_t find_last(const K* pattern, size_t lenPattern, size_t offset) const noexcept {
|
||
if (lenPattern == 1)
|
||
return find_last(pattern[0], offset);
|
||
size_t lenText = std::min(_len(), offset);
|
||
// Образец, не вмещающийся в строку и пустой образец не находим
|
||
// We don't look for an empty line or a line longer than the text.
|
||
if (!lenPattern || lenPattern > lenText)
|
||
return str::npos;
|
||
|
||
lenPattern--;
|
||
const K *text = _str() + lenPattern, last = pattern[lenPattern];
|
||
lenText -= lenPattern;
|
||
while(lenText) {
|
||
if (text[--lenText] == last) {
|
||
if (traits::compare(text + lenText - lenPattern, pattern, lenPattern) == 0) {
|
||
return lenText;
|
||
}
|
||
}
|
||
}
|
||
return str::npos;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти начало последнего вхождения подстроки в этой строке.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - c какой позиции вести поиск в обратную сторону, -1 - с самого конца.
|
||
* @return size_t - позицию начала вхождения подстроки, или -1, если не найдена.
|
||
* @en @brief Find the beginning of the last occurrence of a substring in this string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to search in the opposite direction, -1 - from the very end.
|
||
* @return size_t - the position of the beginning of the occurrence of the substring, or -1 if not found.
|
||
*/
|
||
size_t find_last(str_piece pattern, size_t offset = -1) const noexcept {
|
||
return find_last(pattern.symbols(), pattern.length(), offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти конец последнего вхождения подстроки в этой строке.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - c какой позиции вести поиск в обратную сторону, -1 - с самого конца.
|
||
* @return size_t - позицию сразу за последним вхождением подстроки, или -1, если не найдена.
|
||
* @en @brief Find the end of the last occurrence of a substring in this string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to search in the opposite direction, -1 - from the very end.
|
||
* @return size_t - the position immediately after the last occurrence of the substring, or -1 if not found.
|
||
*/
|
||
size_t find_end_of_last(str_piece pattern, size_t offset = -1) const noexcept {
|
||
size_t fnd = find_last(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? fnd : fnd + pattern.length();
|
||
}
|
||
/*!
|
||
* @ru @brief Найти начало последнего вхождения подстроки в этой строке или конец строки.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - c какой позиции вести поиск в обратную сторону, -1 - с самого конца.
|
||
* @return size_t - позицию начала вхождения подстроки, или длину строки, если не найдена.
|
||
* @en @brief Find the beginning of the last occurrence of a substring in this string or the end of the string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to search in the opposite direction, -1 - from the very end.
|
||
* @return size_t - the position at which the substring begins, or the length of the string if not found.
|
||
*/
|
||
size_t find_last_or_all(str_piece pattern, size_t offset = -1) const noexcept {
|
||
auto fnd = find_last(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? _len() : fnd;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти конец последнего вхождения подстроки в этой строке или конец строки.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - c какой позиции вести поиск в обратную сторону, -1 - с самого конца.
|
||
* @return size_t - позицию сразу за последним вхождением подстроки, или длину строки, если не найдена.
|
||
* @en @brief Find the end of the last occurrence of a substring in this string, or the end of a string.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to search in the opposite direction, -1 - from the very end.
|
||
* @return size_t - the position immediately after the last occurrence of the substring, or the length of the string if not found.
|
||
*/
|
||
size_t find_end_of_last_or_all(str_piece pattern, size_t offset = -1) const noexcept {
|
||
size_t fnd = find_last(pattern.symbols(), pattern.length(), offset);
|
||
return fnd == str::npos ? _len() : fnd + pattern.length();
|
||
}
|
||
/*!
|
||
* @ru @brief Содержит ли строка указанную подстроку.
|
||
* @param pattern - искомая строка.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return bool.
|
||
* @en @brief Whether the string contains the specified substring.
|
||
* @param pattern - the search string.
|
||
* @param offset - from which position to start the search.
|
||
* @return bool.
|
||
*/
|
||
bool contains(str_piece pattern, size_t offset = 0) const noexcept {
|
||
return find(pattern, offset) != str::npos;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти символ в этой строке.
|
||
* @param s - искомый символ.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию найденного символа, или -1, если не найден.
|
||
* @en @brief Find a character in this string.
|
||
* @param s is an optional character.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - position of the found character, or -1 if not found.
|
||
*/
|
||
size_t find(K s, size_t offset = 0) const noexcept {
|
||
size_t len = _len();
|
||
if (offset < len) {
|
||
const K *str = _str(), *fnd = traits::find(str + offset, len - offset, s);
|
||
if (fnd)
|
||
return static_cast<size_t>(fnd - str);
|
||
}
|
||
return str::npos;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти символ в этой строке или конец строки.
|
||
* @param s - искомый символ.
|
||
* @param offset - с какой позиции начинать поиск.
|
||
* @return size_t - позицию найденного символа, или длину строки, если не найден.
|
||
* @en @brief Find a character in this string or the end of a string.
|
||
* @param s is an optional character.
|
||
* @param offset - from which position to start the search.
|
||
* @return size_t - position of the found character, or string length if not found.
|
||
*/
|
||
size_t find_or_all(K s, size_t offset = 0) const noexcept {
|
||
size_t len = _len();
|
||
if (offset < len) {
|
||
const K *str = _str(), *fnd = traits::find(str + offset, len - offset, s);
|
||
if (fnd)
|
||
return static_cast<size_t>(fnd - str);
|
||
}
|
||
return len;
|
||
}
|
||
|
||
template<typename Op>
|
||
void for_all_finded(const Op& op, const K* pattern, size_t patternLen, size_t offset, size_t maxCount) const {
|
||
if (!maxCount)
|
||
maxCount--;
|
||
while (maxCount-- > 0) {
|
||
size_t fnd = find(pattern, patternLen, offset);
|
||
if (fnd == str::npos)
|
||
break;
|
||
op(fnd);
|
||
offset = fnd + patternLen;
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Вызвать функтор для всех найденных вхождений подстроки в этой строке.
|
||
* @param op - функтор, принимающий строку.
|
||
* @param pattern - искомая подстрока.
|
||
* @param offset - позиция начала поиска.
|
||
* @param maxCount - максимальное количество обрабатываемых вхождений, 0 - без ограничений.
|
||
* @en @brief Call a functor on all found occurrences of a substring in this string.
|
||
* @param op is a functor that takes a string.
|
||
* @param pattern - the substring to search for.
|
||
* @param offset - search start position.
|
||
* @param maxCount - the maximum number of occurrences to be processed, 0 - no restrictions.
|
||
*/
|
||
template<typename Op>
|
||
void for_all_finded(const Op& op, str_piece pattern, size_t offset = 0, size_t maxCount = 0) const {
|
||
for_all_finded(op, pattern.symbols(), pattern.length(), offset, maxCount);
|
||
}
|
||
|
||
std::vector<size_t> find_all(const K* pattern, size_t patternLen, size_t offset, size_t maxCount) const {
|
||
std::vector<size_t> result;
|
||
for_all_finded([&](auto f) { result.push_back(f); }, pattern, patternLen, offset, maxCount);
|
||
return result;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти все вхождения подстроки в этой строке.
|
||
* @param pattern - искомая подстрока.
|
||
* @param offset - позиция начала поиска.
|
||
* @param maxCount - максимальное количество обрабатываемых вхождений, 0 - без ограничений.
|
||
* @return std::vector<size_t> - вектор с позициями начал найденных вхождений.
|
||
* @en @brief Find all occurrences of a substring in this string.
|
||
* @param pattern - the substring to search for.
|
||
* @param offset - search start position.
|
||
* @param maxCount - the maximum number of occurrences to be processed, 0 - no restrictions.
|
||
* @return std::vector<size_t> - a vector with the positions of the beginnings of the found occurrences.
|
||
*/
|
||
std::vector<size_t> find_all(str_piece pattern, size_t offset = 0, size_t maxCount = 0) const {
|
||
return find_all(pattern.symbols(), pattern.length(), offset, maxCount);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти последнее вхождения символа в этой строке.
|
||
* @param s - искомый символ.
|
||
* @param offset - c какой позиции вести поиск в обратную сторону, -1 - с самого конца.
|
||
* @return size_t - позицию найденного символа, или -1, если не найден.
|
||
* @en @brief Find the last occurrence of a character in this string.
|
||
* @param s is an optional character.
|
||
* @param offset - from which position to search in the opposite direction, -1 - from the very end.
|
||
* @return size_t - position of the found character, or -1 if not found.
|
||
*/
|
||
size_t find_last(K s, size_t offset = -1) const noexcept {
|
||
size_t len = std::min(_len(), offset);
|
||
const K *text = _str();
|
||
while (len > 0) {
|
||
if (text[--len] == s)
|
||
return len;
|
||
}
|
||
return str::npos;
|
||
}
|
||
/*!
|
||
* @ru @brief Найти первое вхождение символа из заданного набора символов.
|
||
* @param pattern - строка, задающая набор искомых символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return size_t - позицию найденного вхождения, или -1, если не найден.
|
||
* @en @brief Find the first occurrence of a character from a given character set.
|
||
* @param pattern - a string specifying the set of characters to search for.
|
||
* @param offset - search start position.
|
||
* @return size_t - position of the found occurrence, or -1 if not found.
|
||
*/
|
||
size_t find_first_of(str_piece pattern, size_t offset = 0) const noexcept {
|
||
return std::string_view{_str(), _len()}.find_first_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти первое вхождение символа из заданного набора символов.
|
||
* @param pattern - строка, задающая набор искомых символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return std::pair<size_t, size_t> - пару из позиции найденного вхождения и номера найденного символа в наборе, или -1, если не найден.
|
||
* @en @brief Find the first occurrence of a character from a given character set.
|
||
* @param pattern - a string specifying the set of characters to search for.
|
||
* @param offset - search start position.
|
||
* @return std::pair<size_t, size_t> - a pair from the position of the found occurrence and the number of the found character in the set, or -1 if not found.
|
||
*/
|
||
std::pair<size_t, size_t> find_first_of_idx(str_piece pattern, size_t offset = 0) const noexcept {
|
||
const K* text = _str();
|
||
size_t fnd = std::string_view{text, _len()}.find_first_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
return {fnd, fnd == std::string::npos ? fnd : pattern.find(text[fnd]) };
|
||
}
|
||
/*!
|
||
* @ru @brief Найти первое вхождение символа не из заданного набора символов.
|
||
* @param pattern - строка, задающая набор символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return size_t - позицию найденного вхождения, или -1, если не найден.
|
||
* @en @brief Find the first occurrence of a character not from the given character set.
|
||
* @param pattern - a string specifying the character set.
|
||
* @param offset - search start position.
|
||
* @return size_t - position of the found occurrence, or -1 if not found.
|
||
*/
|
||
size_t find_first_not_of(str_piece pattern, size_t offset = 0) const noexcept {
|
||
return std::string_view{_str(), _len()}.find_first_not_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти последнее вхождение символа из заданного набора символов.
|
||
* @param pattern - строка, задающая набор искомых символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return size_t - позицию найденного вхождения, или -1, если не найден.
|
||
* @en @brief Find the last occurrence of a character from a given character set.
|
||
* @param pattern - a string specifying the set of characters to search for.
|
||
* @param offset - search start position.
|
||
* @return size_t - position of the found occurrence, or -1 if not found.
|
||
*/
|
||
size_t find_last_of(str_piece pattern, size_t offset = str::npos) const noexcept {
|
||
return std::string_view{_str(), _len()}.find_last_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Найти последнее вхождение символа из заданного набора символов.
|
||
* @param pattern - строка, задающая набор искомых символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return std::pair<size_t, size_t> - пару из позиции найденного вхождения и номера найденного символа в наборе, или -1, если не найден.
|
||
* @en @brief Find the last occurrence of a character from a given character set.
|
||
* @param pattern - a string specifying the set of characters to search for.
|
||
* @param offset - search start position.
|
||
* @return std::pair<size_t, size_t> - a pair from the position of the found occurrence and the number of the found character in the set, or -1 if not found.
|
||
*/
|
||
std::pair<size_t, size_t> find_last_of_idx(str_piece pattern, size_t offset = str::npos) const noexcept {
|
||
const K* text = _str();
|
||
size_t fnd = std::string_view{text, _len()}.find_last_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
return {fnd, fnd == std::string::npos ? fnd : pattern.find(text[fnd]) };
|
||
}
|
||
/*!
|
||
* @ru @brief Найти последнее вхождение символа не из заданного набора символов.
|
||
* @param pattern - строка, задающая набор символов.
|
||
* @param offset - позиция начала поиска.
|
||
* @return size_t - позицию найденного вхождения, или -1, если не найден.
|
||
* @en @brief Find the last occurrence of a character not from the given character set.
|
||
* @param pattern - a string specifying the character set.
|
||
* @param offset - search start position.
|
||
* @return size_t - position of the found occurrence, or -1 if not found.
|
||
*/
|
||
size_t find_last_not_of(str_piece pattern, size_t offset = str::npos) const noexcept {
|
||
return std::string_view{_str(), _len()}.find_last_not_of(std::string_view{pattern.str, pattern.len}, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить подстроку. Работает аналогично operator(), только результат выдает того же типа, к которому применён метод.
|
||
* @param from - количество символов от начала строки. Если меньше нуля, отсчитывается от конца строки в сторону начала.
|
||
* @param len - количество символов в получаемом "куске". Если меньше или равно нулю, то отсчитать len символов от конца строки.
|
||
* @return my_type - подстроку, объект того же типа, к которому применён метод.
|
||
* @en @brief Get a substring. Works similarly to operator(), only the result is the same type as the method applied to.
|
||
* @param from - number of characters from the beginning of the line. If less than zero, it is counted from the end of the line towards the beginning.
|
||
* @param len - the number of characters in the resulting "chunk". If less than or equal to zero, then count len characters from the end of the line.
|
||
* @return my_type - a substring, an object of the same type to which the method is applied.
|
||
*/
|
||
my_type substr(ptrdiff_t from, ptrdiff_t len = 0) const { // индексация в code units | indexing in code units
|
||
return my_type{d()(from, len)};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить часть строки объектом того же типа, к которому применён метод, аналогично mid.
|
||
* @param from - количество символов от начала строки. При превышении размера строки вернёт пустую строку.
|
||
* @param len - количество символов в получаемом "куске". При выходе за пределы строки вернёт всё до конца строки.
|
||
* @return Строку того же типа, к которому применён метод.
|
||
* @en @brief Get part of a string with an object of the same type to which the method is applied, similar to mid.
|
||
* @param from - number of characters from the beginning of the line. If the string size is exceeded, it will return an empty string.
|
||
* @param len - the number of characters in the resulting "chunk". When going beyond the line, it will return everything up to the end of the line.
|
||
* @return A string of the same type to which the method is applied.
|
||
*/
|
||
my_type str_mid(size_t from, size_t len = -1) const { // индексация в code units | indexing in code units
|
||
return my_type{d().mid(from, len)};
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать строку в число заданного типа.
|
||
* @tparam T - желаемый тип числа.
|
||
* @tparam CheckOverflow - проверять на переполнение.
|
||
* @tparam Base - основание счисления числа, от -1 до 36, кроме 1.
|
||
* - Если 0: то пытается определить основание по префиксу 0[xX] как 16, 0 как 8, иначе 10.
|
||
* - Если -1: то пытается определить основание по префиксам:
|
||
* - 0 или 0[oO]: 8
|
||
* - 0[bB]: 2
|
||
* - 0[xX]: 16
|
||
* - в остальных случаях 10.
|
||
* @tparam SkipWs - пропускать пробельные символы в начале строки.
|
||
* @tparam AllowSign - допустим ли знак '+' перед числом.
|
||
* @return T - число, результат преобразования, насколько оно получилось, или 0 при переполнении.
|
||
* @en @brief Convert a string to a number of the given type.
|
||
* @tparam T - the desired number type.
|
||
* @tparam CheckOverflow - check for overflow.
|
||
* @tparam Base - the base of the number, from -1 to 36, except 1.
|
||
* - If 0: then tries to determine the base by the prefix 0[xX] as 16, 0 as 8, otherwise 10.
|
||
* - If -1: then tries to determine the base by prefixes:
|
||
* - 0 or 0[oO]: 8
|
||
* - 0[bB]: 2
|
||
* - 0[xX]: 16
|
||
* - in other cases 10.
|
||
* @tparam SkipWs - skip whitespace characters at the beginning of the line.
|
||
* @tparam AllowSign - whether the '+' sign is allowed before a number.
|
||
* @return T - a number, the result of the transformation, how much it turned out, or 0 if it overflows.
|
||
*/
|
||
template<ToIntNumber T, bool CheckOverflow = true, unsigned Base = 0, bool SkipWs = true, bool AllowSign = true>
|
||
T as_int() const noexcept {
|
||
auto [res, err, _] = int_convert::to_integer<K, T, Base, CheckOverflow, SkipWs, AllowSign>(_str(), _len());
|
||
return err == IntConvertResult::Overflow ? 0 : res;
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать строку в число заданного типа.
|
||
* @tparam T - желаемый тип числа.
|
||
* @tparam CheckOverflow - проверять на переполнение.
|
||
* @tparam Base - основание счисления числа, от -1 до 36, кроме 1.
|
||
* - Если 0: то пытается определить основание по префиксу 0[xX] как 16, 0 как 8, иначе 10
|
||
* - Если -1: то пытается определить основание по префиксам:
|
||
* - 0 или 0[oO]: 8
|
||
* - 0[bB]: 2
|
||
* - 0[xX]: 16
|
||
* - в остальных случаях 10.
|
||
* @tparam SkipWs - пропускать пробельные символы в начале строки. Пропускаются все символы с ASCII кодами <= 32.
|
||
* @tparam AllowSign - допустим ли знак '+' перед числом.
|
||
* @return std::tuple<T, IntConvertResult, size_t> - кортеж из полученного числа, успешности преобразования и количестве обработанных символов.
|
||
* @en @brief Convert a string to a number of the given type.
|
||
* @tparam T - the desired number type.
|
||
* @tparam CheckOverflow - check for overflow.
|
||
* @tparam Base - the base of the number, from -1 to 36, except 1.
|
||
* - If 0: then tries to determine the base by the prefix 0[xX] as 16, 0 as 8, otherwise 10
|
||
* - If -1: then tries to determine the base by prefixes:
|
||
* - 0 or 0[oO]: 8
|
||
* - 0[bB]: 2
|
||
* - 0[xX]: 16
|
||
* - in other cases 10.
|
||
* @tparam SkipWs - skip whitespace characters at the beginning of the line. All characters with ASCII codes <= 32 are skipped.
|
||
* @tparam AllowSign - whether the '+' sign is allowed before a number.
|
||
* @return std::tuple<T, IntConvertResult, size_t> - a tuple of the received number, the success of the conversion and the number of characters processed.
|
||
*/
|
||
template<ToIntNumber T, bool CheckOverflow = true, unsigned Base = 0, bool SkipWs = true, bool AllowSign = true>
|
||
std::tuple<T, IntConvertResult, size_t> to_int() const noexcept {
|
||
return int_convert::to_integer<K, T, Base, CheckOverflow, SkipWs, AllowSign>(_str(), _len());
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать строку в double.
|
||
* @return double. Пока работает только для строк из char, wchar_t и типов, совместимых с wchar_t по размеру.
|
||
* @en @brief Convert string to double.
|
||
* @return double. So far it only works for strings of char, wchar_t and types compatible with wchar_t in size.
|
||
*/
|
||
double to_double() const noexcept {
|
||
static_assert(sizeof(K) == 1 || sizeof(K) == sizeof(wchar_t), "Only char and wchar available for conversion to double now");
|
||
size_t len = _len();
|
||
if (len) {
|
||
const size_t copyLen = 64;
|
||
K buf[copyLen + 1];
|
||
const K* ptr = _str();
|
||
if (ptr[len] != 0) {
|
||
while (len && uns_type(*ptr) <= ' ') {
|
||
len--;
|
||
ptr++;
|
||
}
|
||
if (len) {
|
||
len = std::min(copyLen, len);
|
||
traits::copy(buf, ptr, len);
|
||
buf[len] = 0;
|
||
ptr = buf;
|
||
}
|
||
}
|
||
if (len) {
|
||
#ifdef _MSC_VER
|
||
static const _locale_t lc = _wcreate_locale(LC_NUMERIC, L"C");
|
||
if constexpr (sizeof(K) == 1) {
|
||
return _strtod_l(ptr, nullptr, lc);
|
||
}
|
||
if constexpr (sizeof(K) == sizeof(wchar_t)) {
|
||
return _wcstod_l((const wchar_t*)ptr, nullptr, lc);
|
||
}
|
||
#else
|
||
if constexpr (sizeof(K) == 1) {
|
||
return std::strtod(ptr, nullptr);
|
||
} else if constexpr (sizeof(K) == sizeof(wchar_t)) {
|
||
return std::wcstod((const wchar_t*)ptr, nullptr);
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
return 0.0;
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Преобразовать строку в целое число.
|
||
* @tparam T - тип числа, выводится из аргумента.
|
||
* @param t - переменная, в которую записывается результат.
|
||
* @en @brief Convert a string to an integer.
|
||
* @tparam T - number type, inferred from the argument.
|
||
* @param t - the variable into which the result is written.
|
||
*/
|
||
template<ToIntNumber T>
|
||
void as_number(T& t) {
|
||
t = as_int<T>();
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать строку в double.
|
||
* @param t - переменная, в которую записывается результат.
|
||
* @en @brief Convert string to double.
|
||
* @param t - the variable into which the result is written.
|
||
*/
|
||
void as_number(double& t) {
|
||
t = to_double();
|
||
}
|
||
|
||
template<typename T, typename Op>
|
||
T splitf(const K* delimeter, size_t lenDelimeter, const Op& beforeFunc, size_t offset) const {
|
||
size_t mylen = _len();
|
||
std::conditional_t<std::is_same_v<T, void>, char, T> results;
|
||
str_piece me{_str(), mylen};
|
||
for (int i = 0;; i++) {
|
||
size_t beginOfDelim = find(delimeter, lenDelimeter, offset);
|
||
if (beginOfDelim == str::npos) {
|
||
str_piece last{me.symbols() + offset, me.length() - offset};
|
||
if constexpr (std::is_invocable_v<Op, str_piece&>) {
|
||
beforeFunc(last);
|
||
}
|
||
if constexpr (requires { results.emplace_back(last); }) {
|
||
if (last.is_same(me)) {
|
||
// Пробуем положить весь объект.
|
||
// Try to put the entire object.
|
||
results.emplace_back(d());
|
||
} else {
|
||
results.emplace_back(last);
|
||
}
|
||
} else if constexpr (requires { results.push_back(last); }) {
|
||
if (last.is_same(me)) {
|
||
// Пробуем положить весь объект.
|
||
// Try to put the entire object.
|
||
results.push_back(d());
|
||
} else {
|
||
results.push_back(last);
|
||
}
|
||
} else if constexpr (requires {results[i] = last;} && requires{std::size(results);}) {
|
||
if (i < std::size(results)) {
|
||
if (last.is_same(me)) {
|
||
// Пробуем положить весь объект.
|
||
// Try to put the entire object.
|
||
results[i] = d();
|
||
} else
|
||
results[i] = last;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
str_piece piece{me.symbols() + offset, beginOfDelim - offset};
|
||
if constexpr (std::is_invocable_v<Op, str_piece&>) {
|
||
beforeFunc(piece);
|
||
}
|
||
if constexpr (requires { results.emplace_back(piece); }) {
|
||
results.emplace_back(piece);
|
||
} else if constexpr (requires { results.push_back(piece); }) {
|
||
results.push_back(piece);
|
||
} else if constexpr (requires { results[i] = piece; } && requires{std::size(results);}) {
|
||
if (i < std::size(results)) {
|
||
results[i] = piece;
|
||
if (i == results.size() - 1) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
offset = beginOfDelim + lenDelimeter;
|
||
}
|
||
if constexpr (!std::is_same_v<T, void>) {
|
||
return results;
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Разделить строку на части по заданному разделителю, с возможным применением функтора к каждой подстроке.
|
||
* @tparam T - тип контейнера для складывания подстрок.
|
||
* @param delimeter - подстрока разделитель.
|
||
* @param beforeFunc - функтор для применения к найденным подстрокам, перед помещением их в результат.
|
||
* @param offset - позиция начала поиска разделителя.
|
||
* @return T - результат.
|
||
* @details Для каждой найденной подстроки, если функтор может принять её, вызывается функтор, и подстрока
|
||
* присваивается результату функтора. Далее подстрока пытается добавиться в результат,
|
||
* вызывая один из его методов - `emplace_back`, `push_back`, `operator[]`. Если ни одного этого метода
|
||
* нет, ничего не делается, только вызов функтора.
|
||
* `operator[]` пытается применится, если у результата можно получить размер через `std::size` и
|
||
* мы не выходим за этот размер.
|
||
* При этом, если найденная подстрока получается совпадающей со всей строкой - в результат пытается
|
||
* поместить не подстроку, а весь объект строки, что позволяет, например, эффективно копировать sstring.
|
||
* @en @brief Split a string into parts at a given delimiter, possibly applying a functor to each substring.
|
||
* @tparam T - type of container for folding substrings.
|
||
* @param delimeter - substring delimiter.
|
||
* @param beforeFunc - a functor to apply to the found substrings, before placing them in the result.
|
||
* @param offset - the position to start searching for the separator.
|
||
* @return T - result.
|
||
* @details For each substring found, if the functor can accept it, the functor is called, and the substring
|
||
* is assigned to the result of the functor. Next, the substring tries to be added to the result,
|
||
* calling one of its methods - `emplace_back`, `push_back`, `operator[]`. If none of this method
|
||
* no, nothing is done, just calling the functor.
|
||
* `operator[]` tries to apply if the result can have a size via `std::size` and
|
||
* we do not exceed this size.
|
||
* At the same time, if the found substring turns out to match the entire string, the result is attempted
|
||
* place not a substring, but the entire string object, which allows, for example, to effectively copy sstring.
|
||
*/
|
||
template<typename T, typename Op>
|
||
T splitf(str_piece delimeter, const Op& beforeFunc, size_t offset = 0) const {
|
||
return splitf<T>(delimeter.symbols(), delimeter.length(), beforeFunc, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Разделить строку на подстроки по заданному разделителю.
|
||
* @tparam T - тип контейнера для результата.
|
||
* @param delimeter - разделитель.
|
||
* @param offset - позиция начала поиска разделителя.
|
||
* @return T - контейнер с результатом.
|
||
* @en @brief Split a string into substrings using a given delimiter.
|
||
* @tparam T - container type for the result.
|
||
* @param delimeter - delimiter.
|
||
* @param offset - the position to start searching for the separator.
|
||
* @return T - container with the result.
|
||
*/
|
||
template<typename T>
|
||
T split(str_piece delimeter, size_t offset = 0) const {
|
||
return splitf<T>(delimeter.symbols(), delimeter.length(), 0, offset);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить объект `Splitter` по заданному разделителю, который позволяет последовательно
|
||
* получать подстроки методом `next()`, пока `is_done()` false.
|
||
* @param delimeter - разделитель.
|
||
* @return Splitter<K>.
|
||
* @en @brief Retrieve a `Splitter` object by the given splitter, which allows sequential
|
||
* get substrings using the `next()` method while `is_done()` is false.
|
||
* @param delimeter - delimiter.
|
||
* @return Splitter<K>.
|
||
*/
|
||
Splitter<K> splitter(str_piece delimeter) const;
|
||
|
||
// Начинается ли эта строка с указанной подстроки
|
||
// Does this string start with the specified substring
|
||
constexpr bool starts_with(const K* prefix, size_t l) const noexcept {
|
||
return _len() >= l && 0 == traits::compare(_str(), prefix, l);
|
||
}
|
||
/*!
|
||
* @ru @brief Начинается ли строка с заданной подстроки.
|
||
* @param prefix - подстрока.
|
||
* @en @brief Whether the string begins with the given substring.
|
||
* @param prefix - substring.
|
||
*/
|
||
constexpr bool starts_with(str_piece prefix) const noexcept {
|
||
return starts_with(prefix.symbols(), prefix.length());
|
||
}
|
||
|
||
constexpr bool starts_with_ia(const K* prefix, size_t len) const noexcept {
|
||
size_t myLen = _len();
|
||
if (myLen < len) {
|
||
return false;
|
||
}
|
||
const K* ptr1 = _str();
|
||
while (len--) {
|
||
K s1 = *ptr1++, s2 = *prefix++;
|
||
if (s1 == s2)
|
||
continue;
|
||
if (makeAsciiLower(s1) != makeAsciiLower(s2))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
/*!
|
||
* @ru @brief Начинается ли строка с заданной подстроки без учёта регистра ASCII символов.
|
||
* @param prefix - подстрока.
|
||
* @en @brief Whether the string begins with the given substring in a case-insensitive ASCII character.
|
||
* @param prefix - substring.
|
||
*/
|
||
constexpr bool starts_with_ia(str_piece prefix) const noexcept {
|
||
return starts_with_ia(prefix.symbols(), prefix.length());
|
||
}
|
||
// Начинается ли эта строка с указанной подстроки без учета unicode регистра
|
||
// Does this string begin with the specified substring, insensitive to unicode case
|
||
bool starts_with_iu(const K* prefix, size_t len) const noexcept {
|
||
return _len() >= len && 0 == uni::compareiu(_str(), len, prefix, len);
|
||
}
|
||
/*!
|
||
* @ru @brief Начинается ли строка с заданной подстроки без учёта регистра Unicode символов первой плоскости (<0xFFFF).
|
||
* @param prefix - подстрока.
|
||
* @en @brief Whether the string starts with the given substring, case-insensitive Unicode characters of the first plane (<0xFFFF).
|
||
* @param prefix - substring.
|
||
*/
|
||
bool starts_with_iu(str_piece prefix) const noexcept {
|
||
return starts_with_iu(prefix.symbols(), prefix.length());
|
||
}
|
||
|
||
// Является ли эта строка началом указанной строки
|
||
// Is this string the beginning of the specified string
|
||
constexpr bool prefix_in(const K* text, size_t len) const noexcept {
|
||
size_t myLen = _len();
|
||
if (myLen > len)
|
||
return false;
|
||
return !myLen || 0 == traits::compare(text, _str(), myLen);
|
||
}
|
||
/*!
|
||
* @ru @brief Является ли эта строка началом другой строки.
|
||
* @param text - другая строка.
|
||
* @en @brief Whether this string is the beginning of another string.
|
||
* @param text - another string.
|
||
*/
|
||
constexpr bool prefix_in(str_piece text) const noexcept {
|
||
return prefix_in(text.symbols(), text.length());
|
||
}
|
||
// Заканчивается ли строка указанной подстрокой
|
||
// Does the string end with the specified substring
|
||
constexpr bool ends_with(const K* suffix, size_t len) const noexcept {
|
||
size_t myLen = _len();
|
||
return len <= myLen && traits::compare(_str() + myLen - len, suffix, len) == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Заканчивается ли строка указанной подстрокой.
|
||
* @param suffix - подстрока.
|
||
* @en @brief Whether the string ends with the specified substring.
|
||
* @param suffix - substring.
|
||
*/
|
||
constexpr bool ends_with(str_piece suffix) const noexcept {
|
||
return ends_with(suffix.symbols(), suffix.length());
|
||
}
|
||
// Заканчивается ли строка указанной подстрокой без учета регистра ASCII
|
||
// Whether the string ends with the specified substring, case insensitive ASCII
|
||
constexpr bool ends_with_ia(const K* suffix, size_t len) const noexcept {
|
||
size_t myLen = _len();
|
||
if (myLen < len) {
|
||
return false;
|
||
}
|
||
const K* ptr1 = _str() + myLen - len;
|
||
while (len--) {
|
||
K s1 = *ptr1++, s2 = *suffix++;
|
||
if (s1 == s2)
|
||
continue;
|
||
if (makeAsciiLower(s1) != makeAsciiLower(s2))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
/*!
|
||
* @ru @brief Заканчивается ли строка указанной подстрокой без учёта регистра ASCII символов.
|
||
* @param suffix - подстрока.
|
||
* @en @brief Whether the string ends with the specified substring in a case-insensitive ASCII character.
|
||
* @param suffix - substring.
|
||
*/
|
||
constexpr bool ends_with_ia(str_piece suffix) const noexcept {
|
||
return ends_with_ia(suffix.symbols(), suffix.length());
|
||
}
|
||
// Заканчивается ли строка указанной подстрокой без учета регистра UNICODE
|
||
// Whether the string ends with the specified substring, case insensitive UNICODE
|
||
constexpr bool ends_with_iu(const K* suffix, size_t len) const noexcept {
|
||
size_t myLen = _len();
|
||
return myLen >= len && 0 == uni::compareiu(_str() + myLen - len, len, suffix, len);
|
||
}
|
||
/*!
|
||
* @ru @brief Заканчивается ли строка указанной подстрокой без учёта регистра Unicode символов первой плоскости (<0xFFFF).
|
||
* @param suffix - подстрока.
|
||
* @en @brief Whether the string ends with the specified substring, case-insensitive Unicode characters of the first plane (<0xFFFF).
|
||
* @param suffix - substring.
|
||
*/
|
||
constexpr bool ends_with_iu(str_piece suffix) const noexcept {
|
||
return ends_with_iu(suffix.symbols(), suffix.length());
|
||
}
|
||
/*!
|
||
* @ru @brief Содержит ли строка только ASCII символы.
|
||
* @en @brief Whether the string contains only ASCII characters.
|
||
*/
|
||
bool is_ascii() const noexcept {
|
||
if (_is_empty())
|
||
return true;
|
||
const int sl = ascii_mask<K>::WIDTH;
|
||
const size_t mask = ascii_mask<K>::VALUE;
|
||
size_t len = _len();
|
||
const uns_type* ptr = reinterpret_cast<const uns_type*>(_str());
|
||
if constexpr (sl > 1) {
|
||
const size_t roundMask = sizeof(size_t) - 1;
|
||
while (len >= sl && (reinterpret_cast<size_t>(ptr) & roundMask) != 0) {
|
||
if (*ptr++ > 127)
|
||
return false;
|
||
len--;
|
||
}
|
||
while (len >= sl) {
|
||
if (*reinterpret_cast<const size_t*>(ptr) & mask)
|
||
return false;
|
||
ptr += sl;
|
||
len -= sl;
|
||
}
|
||
}
|
||
while (len--) {
|
||
if (*ptr++ > 127)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить копию строки в верхнем регистре ASCII символов.
|
||
* @tparam R - желаемый тип строки, по умолчанию тот же, чей метод вызывался.
|
||
* @return R - копию строки в верхнем регистре.
|
||
* @en @brief Get a copy of the string in uppercase ASCII characters.
|
||
* @tparam R - the desired string type, by default the same whose method was called.
|
||
* @return R - uppercase copy of the string.
|
||
*/
|
||
template<typename R = my_type>
|
||
R uppered_only_ascii() const {
|
||
return R::uppered_only_ascii_from(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить копию строки в нижнем регистре ASCII символов.
|
||
* @tparam R - желаемый тип строки, по умолчанию тот же, чей метод вызывался.
|
||
* @return R - копию строки в нижнем регистре.
|
||
* @en @brief Get a copy of the string in lowercase ASCII characters.
|
||
* @tparam R - the desired string type, by default the same whose method was called.
|
||
* @return R - lowercase copy of the string.
|
||
*/
|
||
template<typename R = my_type>
|
||
R lowered_only_ascii() const {
|
||
return R::lowered_only_ascii_from(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить копию строки в верхнем регистре Unicode символов первой плоскости (<0xFFFF).
|
||
* @tparam R - желаемый тип строки, по умолчанию тот же, чей метод вызывался.
|
||
* @return R - копию строки в верхнем регистре.
|
||
* @en @brief Get a copy of the string in upper case Unicode characters of the first plane (<0xFFFF).
|
||
* @tparam R - the desired string type, by default the same whose method was called.
|
||
* @return R - uppercase copy of the string.
|
||
*/
|
||
template<typename R = my_type>
|
||
R uppered() const {
|
||
return R::uppered_from(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить копию строки в нижнем регистре Unicode символов первой плоскости (<0xFFFF).
|
||
* @tparam R - желаемый тип строки, по умолчанию тот же, чей метод вызывался.
|
||
* @return R - копию строки в нижнем регистре.
|
||
* @en @brief Get a copy of the string in lowercase Unicode characters of the first plane (<0xFFFF).
|
||
* @tparam R - the desired string type, by default the same whose method was called.
|
||
* @return R - lowercase copy of the string.
|
||
*/
|
||
template<typename R = my_type>
|
||
R lowered() const {
|
||
return R::lowered_from(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить копию строки с заменёнными вхождениями подстрок.
|
||
* @tparam R - желаемый тип строки, по умолчанию тот же, чей метод вызывался.
|
||
* @param pattern - искомая подстрока.
|
||
* @param repl - строка, на которую заменять.
|
||
* @param offset - начальная позиция поиска.
|
||
* @param maxCount - максимальное количество замен, 0 - без ограничений.
|
||
* @return R строку заданного типа, по умолчанию того же, чей метод вызывался.
|
||
* @en @brief Get a copy of the string with occurrences of substrings replaced.
|
||
* @tparam R - the desired string type, by default the same whose method was called.
|
||
* @param pattern - the substring to search for.
|
||
* @param repl - the string to replace with.
|
||
* @param offset - starting position of the search.
|
||
* @param maxCount - maximum number of replacements, 0 - no restrictions.
|
||
* @return R a string of the given type, by default the same whose method was called.
|
||
*/
|
||
template<typename R = my_type>
|
||
R replaced(str_piece pattern, str_piece repl, size_t offset = 0, size_t maxCount = 0) const {
|
||
return R::replaced_from(d(), pattern, repl, offset, maxCount);
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Получить строковое выражение, которое выдает строку с заменёнными подстроками, заданными строковыми литералами.
|
||
* @param pattern - строковый литерал, подстрока, которую меняем.
|
||
* @param repl - строковый литерал, подстрока, на которую меняем.
|
||
* @return строковое выражение, заменяющее подстроки.
|
||
* @en @brief Get a string expression that produces a string with replaced substrings given by string literals.
|
||
* @param pattern - string literal, substring to be changed.
|
||
* @param repl - string literal, substring to change to.
|
||
* @return a string expression that replaces substrings.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count, typename M, size_t L = const_lit_for<K, M>::Count>
|
||
expr_replaces<K, N - 1, L - 1> replace_init(T&& pattern, M&& repl) const {
|
||
return expr_replaces<K, N - 1, L - 1>{d(), pattern, repl};
|
||
}
|
||
|
||
template<StrType<K> From>
|
||
static my_type make_trim_op(const From& from, const auto& opTrim) {
|
||
str_piece sfrom = from, newPos = opTrim(sfrom);
|
||
return newPos.is_same(sfrom) ? my_type{from} : my_type{newPos};
|
||
}
|
||
template<TrimSides S, StrType<K> From>
|
||
static my_type trim_static(const From& from) {
|
||
return make_trim_op(from, trim_operator<S, K, static_cast<size_t>(-1), true>{});
|
||
}
|
||
|
||
template<TrimSides S, bool withSpaces, typename T, size_t N = const_lit_for<K, T>::Count, StrType<K> From>
|
||
requires is_const_pattern<N>
|
||
static my_type trim_static(const From& from, T&& pattern) {
|
||
return make_trim_op(from, trim_operator<S, K, N - 1, withSpaces>{pattern});
|
||
}
|
||
|
||
template<TrimSides S, bool withSpaces, StrType<K> From>
|
||
static my_type trim_static(const From& from, str_piece pattern) {
|
||
return make_trim_op(from, trim_operator<S, K, 0, withSpaces>{{pattern}});
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением пробельных символов слева и справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @return R - строка, с удалёнными в начале и в конце пробельными символами.
|
||
* @en @brief Get a string with whitespace removed on the left and right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @return R - a string with whitespace characters removed at the beginning and end.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed() const {
|
||
return R::template trim_static<TrimSides::TrimAll>(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением пробельных символов слева.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @return R - строка, с удалёнными в начале пробельными символами.
|
||
* @en @brief Get a string with whitespace removed on the left.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @return R - a string with leading whitespace characters removed.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_left() const {
|
||
return R::template trim_static<TrimSides::TrimLeft>(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением пробельных символов справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @return R - строка, с удалёнными в конце пробельными символами.
|
||
* @en @brief Get a string with whitespace removed on the right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @return R - a string with whitespace characters removed at the end.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_right() const {
|
||
return R::template trim_static<TrimSides::TrimRight>(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, слева и справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале и в конце символами, содержащимися в литерале.
|
||
* @en @brief Get a string with the characters specified by the string literal removed from the left and right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the literal removed at the beginning and at the end.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimAll, false>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, слева.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале символами, содержащимися в литерале.
|
||
* @en @brief Get a string with the characters specified by the string literal removed from the left.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the literal removed at the beginning.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed_left(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimLeft, false>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в конце символами, содержащимися в литерале.
|
||
* @en @brief Get a string with the characters specified by the string literal removed from the right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with characters contained in the literal removed at the end.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed_right(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimRight, false>(d(), pattern);
|
||
}
|
||
// Триминг по символам в литерале и пробелам
|
||
// Trimming by characters in literal and spaces
|
||
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, а также
|
||
* пробельных символов, слева и справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале и в конце символами, содержащимися в литерале
|
||
* и пробельными символами.
|
||
* @en @brief Get a string with the characters specified by the string literal removed, as well as
|
||
* whitespace characters, left and right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the literal removed at the beginning and at the end
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed_with_spaces(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimAll, true>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, а также
|
||
* пробельных символов, слева.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале символами, содержащимися в литерале
|
||
* и пробельными символами.
|
||
* @en @brief Get a string with the characters specified by the string literal removed, as well as
|
||
* whitespace characters, left.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the literal removed at the beginning
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed_left_with_spaces(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimLeft, true>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных строковым литералом, а также
|
||
* пробельных символов, справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строковый литерал, задающий символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в конце символами, содержащимися в литерале
|
||
* и пробельными символами.
|
||
* @en @brief Get a string with the characters specified by the string literal removed, as well as
|
||
* whitespace characters, right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern is a string literal specifying the characters that will be trimmed.
|
||
* @return R - a string with characters contained in the literal removed at the end
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
R trimmed_right_with_spaces(T&& pattern) const {
|
||
return R::template trim_static<TrimSides::TrimRight, true>(d(), pattern);
|
||
}
|
||
// Триминг по динамическому источнику
|
||
// Trimming by dynamic source
|
||
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, слева и справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале и в конце символами, содержащимися в шаблоне.
|
||
* @en @brief Get a string with characters specified by another string removed, left and right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the pattern removed at the beginning and at the end.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimAll, false>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, слева.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале символами, содержащимися в шаблоне.
|
||
* @en @brief Get a string with characters specified by another string removed from the left.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the pattern removed at the beginning.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_left(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimLeft, false>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в конце символами, содержащимися в шаблоне.
|
||
* @en @brief Get a string with characters specified by another string removed to the right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with characters contained in the pattern removed at the end.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_right(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimRight, false>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, а также
|
||
* пробельных символов, слева и справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале и в конце символами, содержащимися в шаблоне
|
||
* и пробельными символами.
|
||
* @en @brief Get a string, removing characters specified by another string, as well as
|
||
* whitespace characters, left and right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the pattern removed at the beginning and at the end
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_with_spaces(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimAll, true>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, а также
|
||
* пробельных символов, слева.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в начале символами, содержащимися в шаблоне
|
||
* и пробельными символами.
|
||
* @en @brief Get a string, removing characters specified by another string, as well as
|
||
* whitespace characters, left.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with the characters contained in the pattern removed at the beginning
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_left_with_spaces(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimLeft, true>(d(), pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку с удалением символов, заданных другой строкой, а также
|
||
* пробельных символов, справа.
|
||
* @tparam R - желаемый тип строки, по умолчанию simple_str.
|
||
* @param pattern - строка, задающая символы, которые будут обрезаться.
|
||
* @return R - строка, с удалёнными в конце символами, содержащимися в шаблоне
|
||
* и пробельными символами.
|
||
* @en @brief Get a string, removing characters specified by another string, as well as
|
||
* whitespace characters, right.
|
||
* @tparam R - desired string type, default simple_str.
|
||
* @param pattern - a string specifying the characters that will be trimmed.
|
||
* @return R - a string with characters contained in the template removed at the end
|
||
* and whitespace characters.
|
||
*/
|
||
template<typename R = str_piece>
|
||
R trimmed_right_with_spaces(str_piece pattern) const {
|
||
return R::template trim_static<TrimSides::TrimRight, true>(d(), pattern);
|
||
}
|
||
};
|
||
|
||
/*
|
||
* Базовая структура с информацией о строке.
|
||
* Это структура для не владеющих строк.
|
||
* Так как здесь только один базовый класс, MSVC компилятор автоматом применяет empty base optimization,
|
||
* в результате размер класса не увеличивается
|
||
* Basic structure with string information.
|
||
* This is the structure for non-owning strings.
|
||
* Since there is only one base class, the MSVC compiler automatically applies empty base optimization,
|
||
* as a result the class size does not increase
|
||
*/
|
||
|
||
/*!
|
||
* @ru @brief Простейший класс иммутабельной не владеющей строки.
|
||
* @details Аналог std::string_view. Содержит только указатель и длину.
|
||
* Как наследник от str_algs поддерживает все константные строковые методы.
|
||
* @tparam K - тип символов строки.
|
||
* @en @brief The simplest immutable non-owning string class.
|
||
* @details Similar to std::string_view. Contains only a pointer and a length.
|
||
* As a descendant of str_algs, it supports all constant string methods.
|
||
* @tparam K - the character type of the string.
|
||
*/
|
||
template<typename K>
|
||
struct simple_str : str_algs<K, simple_str<K>, simple_str<K>, false> {
|
||
using symb_type = K;
|
||
using my_type = simple_str<K>;
|
||
|
||
const symb_type* str;
|
||
size_t len;
|
||
|
||
simple_str() = default;
|
||
|
||
/*!
|
||
* @ru @brief Конструктор из строкового литерала.
|
||
* @en @brief Constructor from a string literal.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
constexpr simple_str(T&& v) noexcept : str(v), len(N - 1) {}
|
||
/*!
|
||
* @ru @brief Конструктор из указателя и длины.
|
||
* @en @brief Constructor from pointer and length.
|
||
*/
|
||
constexpr simple_str(const K* p, size_t l) noexcept : str(p), len(l) {}
|
||
/*!
|
||
* @ru @brief Конструктор, позволяющий инициализировать объектами std::string, и std::string_view
|
||
* при условии, что они lvalue, то есть не временные.
|
||
* @en @brief Constructor that allows you to initialize std::string and std::string_view objects
|
||
* provided that they are lvalue, that is, not temporary.
|
||
*/
|
||
template<typename S>
|
||
requires(std::is_same_v<S, std::string&> || std::is_same_v<S, const std::string&>
|
||
|| std::is_same_v<S, std::string_view&> || std::is_same_v<S, const std::string_view&>)
|
||
constexpr simple_str(S&& s) noexcept : str(s.data()), len(s.length()) {}
|
||
/*!
|
||
* @ru @brief Получить длину строки.
|
||
* @en @brief Get the length of the string.
|
||
*/
|
||
constexpr size_t length() const noexcept {
|
||
return len;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить указатель на константный буфер с символами строки.
|
||
* @en @brief Get a pointer to a constant buffer containing string characters.
|
||
*/
|
||
constexpr const symb_type* symbols() const noexcept {
|
||
return str;
|
||
}
|
||
/*!
|
||
* @ru @brief Проверить, не пуста ли строка.
|
||
* @en @brief Check if a string is empty.
|
||
*/
|
||
constexpr bool is_empty() const noexcept {
|
||
return len == 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Проверить, не указывают ли два объекта на одну строку.
|
||
* @param other - другая строка.
|
||
* @en @brief Check if two objects point to the same string.
|
||
* @param other - another string.
|
||
*/
|
||
bool is_same(simple_str<K> other) const noexcept {
|
||
return str == other.str && len == other.len;
|
||
}
|
||
/*!
|
||
* @ru @brief Проверить, не является ли строка частью другой строки.
|
||
* @param other - другая строка.
|
||
* @en @brief Check if a string is part of another string.
|
||
* @param other - another string.
|
||
*/
|
||
bool is_part_of(simple_str<K> other) const noexcept {
|
||
return str >= other.str && str + len <= other.str + other.len;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить символ из указанной позиции. Проверка границ не выполняется.
|
||
* @param idx - позиция символа.
|
||
* @return K - символ.
|
||
* @en @brief Get the character from the specified position. Bounds checking is not performed.
|
||
* @param idx - position of the symbol.
|
||
* @return K is a symbol.
|
||
*/
|
||
K operator[](size_t idx) const {
|
||
return str[idx];
|
||
}
|
||
/*!
|
||
* @ru @brief Сдвигает начало строки на заданное количество символов.
|
||
* @param delta - количество символов.
|
||
* @return my_type&.
|
||
* @en @brief Shifts the start of a line by the specified number of characters.
|
||
* @param delta - number of characters.
|
||
* @return my_type&.
|
||
*/
|
||
my_type& remove_prefix(size_t delta) {
|
||
str += delta;
|
||
len -= delta;
|
||
return *this;
|
||
}
|
||
/*!
|
||
* @ru @brief Укорачивает строку на заданное количество символов.
|
||
* @param delta - количество символов.
|
||
* @return my_type&.
|
||
* @en @brief Shortens the string by the specified number of characters.
|
||
* @param delta - number of characters.
|
||
* @return my_type&.
|
||
*/
|
||
my_type& remove_suffix(size_t delta) {
|
||
len -= delta;
|
||
return *this;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Класс, заявляющий, что ссылается на нуль-терминированную строку.
|
||
* @tparam K - тип символов строки.
|
||
* @details Служит для показа того, что функция параметром хочет получить
|
||
* строку с нулем в конце, например, ей надо дальше передавать его в
|
||
* стороннее API. Без этого ей надо было бы либо указывать параметром
|
||
* конкретный класс строки, что лишает универсальности, либо приводило бы
|
||
* к постоянным накладным расходам на излишнее копирование строк во временный
|
||
* буфер. Источником нуль-терминированных строк могут быть строковые литералы
|
||
* при компиляции, либо классы, хранящие строки.
|
||
* @en @brief A class that claims to refer to a null-terminated string.
|
||
* @tparam K - the character type of the string.
|
||
* @details Shows what the function wants to receive as a parameter
|
||
* a string with a zero at the end, for example, she needs to further transfer it to
|
||
* third party API. Without this, she would have to either specify the parameter
|
||
* specific string class, which deprives universality, or would lead
|
||
* to the constant overhead of unnecessary copying of string into the temporary
|
||
* buffer. Null-terminated strings can be sourced from string literals
|
||
* during compilation, or classes that store strings.
|
||
*/
|
||
template<typename K>
|
||
struct simple_str_nt : simple_str<K> {
|
||
using symb_type = K;
|
||
using my_type = simple_str_nt<K>;
|
||
using base = simple_str<K>;
|
||
using base::base;
|
||
|
||
constexpr static const K empty_string[1] = {0};
|
||
|
||
simple_str_nt() = default;
|
||
/*!
|
||
* @ru @brief Явный конструктор из С-строки.
|
||
* @param p - указатель на C-строку (нуль-терминированная строка).
|
||
* @details Это единственный конструктор из всех строковых объектов, принимающий C-строку.
|
||
* Вычисляет её длину при инициализации. Все остальные строковые объекты не инициализируются
|
||
* C-строками. Это для того, чтобы `strlen` вызывалась только в одном месте библиотеки,
|
||
* длина C-строки вычислялась только один раз и далее не терялась случайно при передаче между разными
|
||
* типами строковых объектов.
|
||
* @en @brief Explicit constructor from C-string.
|
||
* @param p - pointer to a C-string (null-terminated string).
|
||
* @details This is the only constructor of all string objects that accepts a C-string.
|
||
* Calculates its length upon initialization. All other string objects are not initialized
|
||
* C-strings. This is to ensure that `strlen` is called only in one place in the library,
|
||
* the length of the C-string was calculated only once and was not subsequently lost accidentally when transferred between different
|
||
* types of string objects.
|
||
*/
|
||
template<typename T> requires std::is_same_v<std::remove_const_t<std::remove_pointer_t<std::remove_cvref_t<T>>>, K>
|
||
explicit simple_str_nt(T&& p) noexcept {
|
||
base::len = p ? static_cast<size_t>(base::traits::length(p)) : 0;
|
||
base::str = base::len ? p : empty_string;
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор, позволяющий инициализировать объектами std::string, и std::string_view
|
||
* при условии, что они lvalue, то есть не временные.
|
||
* @en @brief Constructor that allows you to initialize std::string and std::string_view objects
|
||
* provided that they are lvalue, that is, not temporary.
|
||
*/
|
||
template<typename S>
|
||
requires(std::is_same_v<S, std::string&> || std::is_same_v<S, const std::string&>
|
||
|| std::is_same_v<S, std::string_view&> || std::is_same_v<S, const std::string_view&>)
|
||
constexpr simple_str_nt(S&& s) noexcept : base(s) {}
|
||
|
||
static const my_type empty_str;
|
||
/*!
|
||
* @ru @brief Оператор преобразования в нуль-терминированную C-строку.
|
||
* @return const K* - указатель на начало строки.
|
||
* @en @brief Conversion operator to a null-terminated C string.
|
||
* @return const K* - pointer to the beginning of the line.
|
||
*/
|
||
operator const K*() const noexcept {
|
||
return base::str;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить нуль-терминированную строку, сдвинув начало на заданное количество символов.
|
||
* @param from - на сколько символов сдвинуть начало строки.
|
||
* @return my_type.
|
||
* @en @brief Get a null-terminated string by shifting the start by the specified number of characters.
|
||
* @param from - by how many characters to shift the beginning of the line.
|
||
* @return my_type.
|
||
*/
|
||
my_type to_nts(size_t from) {
|
||
if (from > base::len) {
|
||
from = base::len;
|
||
}
|
||
return {base::str + from, base::len - from};
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
inline const simple_str_nt<K> simple_str_nt<K>::empty_str{simple_str_nt<K>::empty_string, 0};
|
||
|
||
using ssa = simple_str<u8s>;
|
||
using ssw = simple_str<wchar_t>;
|
||
using ssu = simple_str<u16s>;
|
||
using ssuu = simple_str<u32s>;
|
||
using stra = simple_str_nt<u8s>;
|
||
using strw = simple_str_nt<wchar_t>;
|
||
using stru = simple_str_nt<u16s>;
|
||
using struu = simple_str_nt<u32s>;
|
||
|
||
/*!
|
||
* @ru @brief Класс для последовательного получения подстрок по заданному разделителю.
|
||
* @tparam K - тип символов.
|
||
* @en @brief Class for sequentially obtaining substrings by a given delimiter.
|
||
* @tparam K - character type.
|
||
*/
|
||
template<typename K>
|
||
class Splitter {
|
||
simple_str<K> text_;
|
||
simple_str<K> delim_;
|
||
|
||
public:
|
||
Splitter(simple_str<K> text, simple_str<K> delim) : text_(text), delim_(delim) {}
|
||
/*!
|
||
* @ru @brief Узнать, не закончились ли подстроки.
|
||
* @en @brief Find out if substrings are running out.
|
||
*/
|
||
bool is_done() const {
|
||
return text_.length() == str::npos;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить следующую подстроку.
|
||
* @return simple_str.
|
||
* @en @brief Get the next substring.
|
||
* @return simple_str.
|
||
*/
|
||
simple_str<K> next() {
|
||
if (!text_.length()) {
|
||
auto ret = text_;
|
||
text_.str++;
|
||
text_.len--;
|
||
return ret;
|
||
} else if (text_.length() == str::npos) {
|
||
return {nullptr, 0};
|
||
}
|
||
size_t pos = text_.find(delim_), next = 0;
|
||
if (pos == str::npos) {
|
||
pos = text_.length();
|
||
next = pos + 1;
|
||
} else {
|
||
next = pos + delim_.length();
|
||
}
|
||
simple_str<K> result{text_.str, pos};
|
||
text_.str += next;
|
||
text_.len -= next;
|
||
return result;
|
||
}
|
||
};
|
||
|
||
template<typename K, typename StrRef, typename Impl, bool Mutable>
|
||
Splitter<K> str_algs<K, StrRef, Impl, Mutable>::splitter(StrRef delimeter) const {
|
||
return Splitter<K>{*this, delimeter};
|
||
}
|
||
|
||
template<typename K, bool withSpaces>
|
||
struct CheckSpaceTrim {
|
||
bool is_trim_spaces(K s) const {
|
||
return s == ' ' || (s >= 9 && s <= 13); // || isspace(s);
|
||
}
|
||
};
|
||
template<typename K>
|
||
struct CheckSpaceTrim<K, false> {
|
||
bool is_trim_spaces(K) const {
|
||
return false;
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct CheckSymbolsTrim {
|
||
simple_str<K> symbols;
|
||
bool is_trim_symbols(K s) const {
|
||
return symbols.len != 0 && simple_str<K>::traits::find(symbols.str, symbols.len, s) != nullptr;
|
||
}
|
||
};
|
||
|
||
template<typename K, size_t N>
|
||
struct CheckConstSymbolsTrim {
|
||
const const_lit_to_array<K, N> symbols;
|
||
|
||
template<typename T, size_t M = const_lit_for<K, T>::Count> requires (M == N + 1)
|
||
constexpr CheckConstSymbolsTrim(T&& s) : symbols(std::forward<T>(s)) {}
|
||
|
||
bool is_trim_symbols(K s) const noexcept {
|
||
return symbols.contain(s);
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct CheckConstSymbolsTrim<K, 0> {
|
||
bool is_trim_symbols(K) const {
|
||
return false;
|
||
}
|
||
};
|
||
|
||
template<typename K, size_t N>
|
||
struct SymbSelector {
|
||
using type = CheckConstSymbolsTrim<K, N>;
|
||
};
|
||
|
||
template<typename K>
|
||
struct SymbSelector<K, 0> {
|
||
using type = CheckSymbolsTrim<K>;
|
||
};
|
||
|
||
template<typename K>
|
||
struct SymbSelector<K, static_cast<size_t>(-1)> {
|
||
using type = CheckConstSymbolsTrim<K, 0>;
|
||
};
|
||
|
||
template<TrimSides S, typename K, size_t N, bool withSpaces>
|
||
struct trim_operator : SymbSelector<K, N>::type, CheckSpaceTrim<K, withSpaces> {
|
||
bool isTrim(K s) const {
|
||
return CheckSpaceTrim<K, withSpaces>::is_trim_spaces(s) || SymbSelector<K, N>::type::is_trim_symbols(s);
|
||
}
|
||
simple_str<K> operator()(simple_str<K> from) const {
|
||
if constexpr ((S & TrimSides::TrimLeft) != 0) {
|
||
while (from.len) {
|
||
if (isTrim(*from.str)) {
|
||
from.str++;
|
||
from.len--;
|
||
} else
|
||
break;
|
||
}
|
||
}
|
||
if constexpr ((S & TrimSides::TrimRight) != 0) {
|
||
const K* back = from.str + from.len - 1;
|
||
while (from.len) {
|
||
if (isTrim(*back)) {
|
||
back--;
|
||
from.len--;
|
||
} else
|
||
break;
|
||
}
|
||
}
|
||
return from;
|
||
}
|
||
};
|
||
|
||
template<TrimSides S, typename K>
|
||
using SimpleTrim = trim_operator<S, K, size_t(-1), true>;
|
||
|
||
using trim_w = SimpleTrim<TrimSides::TrimAll, u16s>;
|
||
using trim_a = SimpleTrim<TrimSides::TrimAll, u8s>;
|
||
using triml_w = SimpleTrim<TrimSides::TrimLeft, u16s>;
|
||
using triml_a = SimpleTrim<TrimSides::TrimLeft, u8s>;
|
||
using trimr_w = SimpleTrim<TrimSides::TrimRight, u16s>;
|
||
using trimr_a = SimpleTrim<TrimSides::TrimRight, u8s>;
|
||
|
||
template<TrimSides S = TrimSides::TrimAll, bool withSpaces = false, typename K, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
inline auto trimOp(T&& pattern) {
|
||
return trim_operator<S, K, N - 1, withSpaces>{pattern};
|
||
}
|
||
|
||
template<TrimSides S = TrimSides::TrimAll, bool withSpaces = false, typename K>
|
||
inline auto trimOp(simple_str<K> pattern) {
|
||
return trim_operator<S, K, 0, withSpaces>{pattern};
|
||
}
|
||
|
||
template<typename Src, typename Dest>
|
||
struct utf_convert_selector;
|
||
|
||
template<>
|
||
struct utf_convert_selector<u8s, u16s> {
|
||
static SIMSTR_API size_t need_len(const u8s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u8s* src, size_t srcLen, u16s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u8s, u32s> {
|
||
static SIMSTR_API size_t need_len(const u8s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u8s* src, size_t srcLen, u32s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u8s, wchar_t> {
|
||
static size_t need_len(const u8s* src, size_t srcLen) {
|
||
return utf_convert_selector<u8s, wchar_type>::need_len(src, srcLen);
|
||
}
|
||
static size_t convert(const u8s* src, size_t srcLen, wchar_t* dest) {
|
||
return utf_convert_selector<u8s, wchar_type>::convert(src, srcLen, to_w(dest));
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u16s, u8s> {
|
||
static SIMSTR_API size_t need_len(const u16s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u16s* src, size_t srcLen, u8s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u16s, u32s> {
|
||
static SIMSTR_API size_t need_len(const u16s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u16s* src, size_t srcLen, u32s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u16s, u16s> {
|
||
// При конвертации char16_t в wchar_t под windows будет вызываться эта реализация
|
||
// When converting char16_t to wchar_t under windows this implementation will be called
|
||
static size_t need_len(const u16s* src, size_t srcLen) {
|
||
return srcLen;
|
||
}
|
||
static size_t convert(const u16s* src, size_t srcLen, u16s* dest) {
|
||
ch_traits<u16s>::copy(dest, src, srcLen + 1);
|
||
return srcLen;
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u32s, u32s> {
|
||
// При конвертации char32_t в wchar_t под linux будет вызываться эта реализация
|
||
// When converting char32_t to wchar_t under Linux, this implementation will be called
|
||
static size_t need_len(const u32s* src, size_t srcLen) {
|
||
return srcLen;
|
||
}
|
||
static size_t convert(const u32s* src, size_t srcLen, u32s* dest) {
|
||
ch_traits<u32s>::copy(dest, src, srcLen + 1);
|
||
return srcLen;
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u16s, wchar_t> {
|
||
static size_t need_len(const u16s* src, size_t srcLen) {
|
||
return utf_convert_selector<u16s, wchar_type>::need_len(src, srcLen);
|
||
}
|
||
static size_t convert(const u16s* src, size_t srcLen, wchar_t* dest) {
|
||
return utf_convert_selector<u16s, wchar_type>::convert(src, srcLen, to_w(dest));
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u32s, u8s> {
|
||
static SIMSTR_API size_t need_len(const u32s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u32s* src, size_t srcLen, u8s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u32s, u16s> {
|
||
static SIMSTR_API size_t need_len(const u32s* src, size_t srcLen);
|
||
static SIMSTR_API size_t convert(const u32s* src, size_t srcLen, u16s* dest);
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<u32s, wchar_t> {
|
||
static size_t need_len(const u32s* src, size_t srcLen) {
|
||
return utf_convert_selector<u32s, wchar_type>::need_len(src, srcLen);
|
||
}
|
||
static size_t convert(const u32s* src, size_t srcLen, wchar_t* dest) {
|
||
return utf_convert_selector<u32s, wchar_type>::convert(src, srcLen, to_w(dest));
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<wchar_t, u8s> {
|
||
static size_t need_len(const wchar_t* src, size_t srcLen) {
|
||
return utf_convert_selector<wchar_type, u8s>::need_len(to_w(src), srcLen);
|
||
}
|
||
static size_t convert(const wchar_t* src, size_t srcLen, u8s* dest) {
|
||
return utf_convert_selector<wchar_type, u8s>::convert(to_w(src), srcLen, dest);
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<wchar_t, u16s> {
|
||
static size_t need_len(const wchar_t* src, size_t srcLen) {
|
||
return utf_convert_selector<wchar_type, u16s>::need_len(to_w(src), srcLen);
|
||
}
|
||
static size_t convert(const wchar_t* src, size_t srcLen, u16s* dest) {
|
||
return utf_convert_selector<wchar_type, u16s>::convert(to_w(src), srcLen, dest);
|
||
}
|
||
};
|
||
|
||
template<>
|
||
struct utf_convert_selector<wchar_t, u32s> {
|
||
static size_t need_len(const wchar_t* src, size_t srcLen) {
|
||
return utf_convert_selector<wchar_type, u32s>::need_len(to_w(src), srcLen);
|
||
}
|
||
static size_t convert(const wchar_t* src, size_t srcLen, u32s* dest) {
|
||
return utf_convert_selector<wchar_type, u32s>::convert(to_w(src), srcLen, dest);
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Базовый класс для строк, могущих конвертироваться из другого типа символов.
|
||
* @tparam K - тип символов.
|
||
* @tparam Impl - конечный класс.
|
||
* @details Конвертация выполняется через UTF преобразование.
|
||
* Считаем, что строки `char` - в UTF-8, `char16_t` - в UTF-16, `char32_t` - в UTF-32.
|
||
* `wchar_t` - под Windows UTF-16, в Linux - UTF-32.
|
||
* @en @brief Base class for strings that can be converted from another character type.
|
||
* @tparam K - character type.
|
||
* @tparam Impl - final class.
|
||
* @details Conversion is performed via UTF conversion.
|
||
* We assume that the strings `char` are in UTF-8, `char16_t` - in UTF-16, `char32_t` - in UTF-32.
|
||
* `wchar_t` - in Windows UTF-16, in Linux - UTF-32.
|
||
*/
|
||
template<typename K, typename Impl>
|
||
class from_utf_convertable {
|
||
protected:
|
||
from_utf_convertable() = default;
|
||
using my_type = Impl;
|
||
/*
|
||
Эти методы должен реализовать класс-наследник.
|
||
вызывается только при создании объекта
|
||
init(size_t size)
|
||
set_size(size_t size)
|
||
*/
|
||
public:
|
||
template<typename O>
|
||
requires(!std::is_same_v<O, K>)
|
||
from_utf_convertable(simple_str<O> init) {
|
||
using worker = utf_convert_selector<O, K>;
|
||
Impl* d = static_cast<Impl*>(this);
|
||
size_t len = init.length();
|
||
if (!len)
|
||
d->create_empty();
|
||
else {
|
||
size_t need = worker::need_len(init.symbols(), len);
|
||
K* str = d->init(need);
|
||
str[need] = 0;
|
||
worker::convert(init.symbols(), len, str);
|
||
}
|
||
}
|
||
template<typename O, typename I, bool M>
|
||
requires(!std::is_same_v<O, K>)
|
||
from_utf_convertable(const str_algs<O, simple_str<O>, I, M>& init) : from_utf_convertable(init.to_str()) {}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Строковое выражение для конвертации строк в разные виды UTF.
|
||
* @tparam From - Тип какой строки конвертируем.
|
||
* @tparam To - В какого типа строку конвертируем.
|
||
* @en @brief String expression to convert strings to different UTF types.
|
||
* @tparam From - The type of which string we are converting.
|
||
* @tparam To - What type of string we convert to.
|
||
*/
|
||
template<typename From, typename To> requires (!std::is_same_v<From, To>)
|
||
struct expr_utf {
|
||
using symb_type = To;
|
||
using worker = utf_convert_selector<From, To>;
|
||
|
||
simple_str<From> source_;
|
||
|
||
size_t length() const noexcept {
|
||
return worker::need_len(source_.symbols(), source_.length());
|
||
}
|
||
To* place(To* ptr) const noexcept {
|
||
return ptr + worker::convert(source_.symbols(), source_.length(), ptr);
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Возвращает строковое выражение, преобразующую строку из одного типа символов
|
||
* в другой тип, через UTF-конвертирование.
|
||
* @tparam To - тип строки, в которую надо конвертировать.
|
||
* @tparam From - тип строки, из которого надо конвертировать. Выводится из аргумента.
|
||
* @param from - строка, из которой надо конвертировать.
|
||
* @en @brief Returns a string expression that converts a string of one character type
|
||
* to another type, via UTF conversion.
|
||
* @tparam To - the type of string to convert to.
|
||
* @tparam From - the type of string to convert from. Derived from the argument.
|
||
* @param from - the string from which to convert.
|
||
*/
|
||
template<typename To, typename From> requires (!std::is_same_v<From, To>)
|
||
auto e_utf(simple_str<From> from) {
|
||
return expr_utf<From, To>{from};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Концепт типа, который может сохранить строку.
|
||
* @en @brief A type concept that can store a string.
|
||
*/
|
||
template<typename A, typename K>
|
||
concept storable_str = requires {
|
||
A::is_str_storable == true;
|
||
std::is_same_v<typename A::symb_type, K>;
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Концепт типа, который может модифицировать хранимую строку.
|
||
* @en @brief A type concept that can modify a stored string.
|
||
*/
|
||
template<typename A, typename K>
|
||
concept mutable_str = storable_str<A, K> && requires { A::is_str_mutable == true; };
|
||
|
||
/*!
|
||
* @ru @brief Концепт типа, который не может модифицировать хранимую строку.
|
||
* @en @brief A type concept that cannot modify a stored string.
|
||
*/
|
||
template<typename A, typename K>
|
||
concept immutable_str = storable_str<A, K> && !mutable_str<A, K>;
|
||
|
||
/*!
|
||
* @ru @brief База для объектов, владеющих строкой.
|
||
* @tparam K - тип символов.
|
||
* @tparam Impl - конечный класс наследник.
|
||
* @tparam Allocator - тип аллокатора.
|
||
* @details По прежнему ничего не знает о том, где наследник хранит строку и её размер.
|
||
* Просто вызывает его методы для получения места, и заполняет его при необходимости.
|
||
* Работает только при создании объекта, не работает с модификацией строки после
|
||
* ее создания и гарантирует, что если вызываются эти методы, объект еще только
|
||
* создается, и какого-либо расшаривания данных еще не было.
|
||
*
|
||
* Эти методы должен реализовать класс-наследник, вызываются только при создании объекта
|
||
* - `K* init(size_t size)` - выделить место для строки указанного размера, вернуть адрес
|
||
* - `void create_empty()` - создать пустой объект
|
||
* - `K* set_size(size_t size)` - перевыделить место для строки, если при создании не угадали
|
||
* нужный размер и место нужно больше или меньше.
|
||
* Содержимое строки нужно оставить.
|
||
* Хотя тип аллокатора и задаётся параметром шаблона, делается это только для проброса
|
||
* его типа в конструкторы, методы аллокатора не вызываются. Если наследник не пользуется
|
||
* аллокатором, а сам в `init` и `set_size` как-то выделяет место, может указать типом аллокатора
|
||
* какой-либо пустой класс.
|
||
* @en @brief The base for the objects that own the string.
|
||
* @tparam K - character type.
|
||
* @tparam Impl - the final class is the successor.
|
||
* @tparam Allocator - type of allocator.
|
||
* @details Still knows nothing about where the heir stores the string and its size.
|
||
* Simply calls its methods to get the space, and fills it as needed.
|
||
* Works only when creating an object, does not work with string modification after
|
||
* its creation and ensures that if these methods are called, the object is only
|
||
* is being created and no data sharing has yet taken place.
|
||
*
|
||
* These methods must be implemented by the descendant class and are called only when an object is created
|
||
* - `K* init(size_t size)` - allocate space for a line of the specified size, return the address
|
||
* - `void create_empty()` - create an empty object
|
||
* - `K* set_size(size_t size)` - re-allocate space for the line if you didn’t guess correctly when creating
|
||
* the size you need and the space you need is larger or smaller.
|
||
* The contents of the line must be left.
|
||
* Although the allocator type is specified by the template parameter, this is done only for forwarding
|
||
* of its type in constructors, allocator methods are not called. If the heir does not use
|
||
* an allocator, and in `init` and `set_size` it somehow allocates space, can indicate the type of the allocator
|
||
* any empty class.
|
||
*/
|
||
template<typename K, typename Impl, typename Allocator>
|
||
class str_storable : protected Allocator {
|
||
public:
|
||
using my_type = Impl;
|
||
using traits = ch_traits<K>;
|
||
using allocator_t = Allocator;
|
||
|
||
protected:
|
||
/*!
|
||
* @ru @brief Получить аллокатор.
|
||
* @en @brief Get the allocator.
|
||
*/
|
||
allocator_t& allocator() {
|
||
return *static_cast<Allocator*>(this);
|
||
}
|
||
const allocator_t& allocator() const {
|
||
return *static_cast<const Allocator*>(this);
|
||
}
|
||
|
||
using uni = unicode_traits<K>;
|
||
|
||
Impl& d() noexcept {
|
||
return *static_cast<Impl*>(this);
|
||
}
|
||
const Impl& d() const noexcept {
|
||
return *static_cast<const Impl*>(this);
|
||
}
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
explicit constexpr str_storable(size_t size, Args&&... args) : Allocator(std::forward<Args>(args)...) {
|
||
if (size)
|
||
d().init(size);
|
||
else
|
||
d().create_empty();
|
||
}
|
||
|
||
template<StrType<K> From, typename Op1, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type changeCaseAscii(const From& f, const Op1& opMakeNeedCase, Args&&... args) {
|
||
my_type result{std::forward<Args>(args)...};
|
||
size_t len = f.length();
|
||
if (len) {
|
||
const K* source = f.symbols();
|
||
K* destination = result.init(len);
|
||
for (size_t l = 0; l < len; l++) {
|
||
destination[l] = opMakeNeedCase(source[l]);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
// GCC до сих пор не даёт делать полную специализацию вложенного шаблонного класса внутри внешнего класса, только частичную.
|
||
// Поэтому добавим фиктивный параметр шаблона, чтобы сделать специализацию для u8s прямо в классе.
|
||
// GCC still does not allow full specialization of a nested template class inside an outer class, only partial.
|
||
// So let's add a dummy template parameter to make the specialization for u8s right in the class.
|
||
template<typename T, bool Dummy = true>
|
||
struct ChangeCase {
|
||
template<typename From, typename Op1, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type changeCase(const From& f, const Op1& opChangeCase, Args&&... args) {
|
||
my_type result{std::forward<Args>(args)...};
|
||
size_t len = f.length();
|
||
if (len) {
|
||
opChangeCase(f.symbols(), len, result.init(len));
|
||
}
|
||
return result;
|
||
}
|
||
};
|
||
// Для utf8 сделаем отдельную спецификацию, так как при смене регистра может изменится длина строки
|
||
// For utf8 we will make a separate specification, since changing the register may change the length of the string
|
||
template<bool Dummy>
|
||
struct ChangeCase<u8s, Dummy> {
|
||
template<typename From, typename Op1, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type changeCase(const From& f, const Op1& opChangeCase, Args&&... args) {
|
||
my_type result{std::forward<Args>(args)...};
|
||
;
|
||
size_t len = f.length();
|
||
if (len) {
|
||
const K* ptr = f.symbols();
|
||
K* pWrite = result.init(len);
|
||
|
||
const u8s* source = ptr;
|
||
u8s* dest = pWrite;
|
||
size_t newLen = opChangeCase(source, len, dest, len);
|
||
if (newLen < len) {
|
||
// Строка просто укоротилась
|
||
// The string was simply shortened
|
||
result.set_size(newLen);
|
||
} else if (newLen > len) {
|
||
// Строка не влезла в буфер.
|
||
// The line did not fit into the buffer.
|
||
size_t readed = static_cast<size_t>(source - ptr);
|
||
size_t writed = static_cast<size_t>(dest - pWrite);
|
||
pWrite = result.set_size(newLen);
|
||
dest = pWrite + writed;
|
||
opChangeCase(source, len - readed, dest, newLen - writed);
|
||
}
|
||
pWrite[newLen] = 0;
|
||
}
|
||
return result;
|
||
}
|
||
};
|
||
|
||
public:
|
||
using s_str = simple_str<K>;
|
||
using s_str_nt = simple_str_nt<K>;
|
||
|
||
inline static constexpr bool is_str_storable = true;
|
||
|
||
/*!
|
||
* @ru @brief Создать пустой объект.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Create an empty object.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
constexpr str_storable(Args&&... args) noexcept(std::is_nothrow_constructible_v<allocator_t, Args...>)
|
||
: Allocator(std::forward<Args>(args)...) {
|
||
d().create_empty();
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Конструктор из другого строкового объекта.
|
||
* @param other - другой строковый объект, simple_str.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief A constructor from another string object.
|
||
* @param other - another string object, simple_str.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
constexpr str_storable(s_str other, Args&&... args) : Allocator(std::forward<Args>(args)...) {
|
||
if (other.length()) {
|
||
K* ptr = d().init(other.length());
|
||
traits::copy(ptr, other.symbols(), other.length());
|
||
ptr[other.length()] = 0;
|
||
} else
|
||
d().create_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор повторения строки.
|
||
* @param repeat - количество повторов.
|
||
* @param pattern - строка, которую надо повторить.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief String repetition constructor.
|
||
* @param repeat - number of repetitions.
|
||
* @param pattern - the line to be repeated.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
constexpr str_storable(size_t repeat, s_str pattern, Args&&... args) : Allocator(std::forward<Args>(args)...) {
|
||
size_t l = pattern.length(), allLen = l * repeat;
|
||
if (allLen) {
|
||
K* ptr = d().init(allLen);
|
||
for (size_t i = 0; i < repeat; i++) {
|
||
traits::copy(ptr, pattern.symbols(), l);
|
||
ptr += l;
|
||
}
|
||
*ptr = 0;
|
||
} else
|
||
d().create_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор повторения символа.
|
||
* @param count - количество повторов.
|
||
* @param pad - символ, который надо повторить.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Character repetition constructor.
|
||
* @param count - number of repetitions.
|
||
* @param pad - the character to be repeated.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
str_storable(size_t count, K pad, Args&&... args) : Allocator(std::forward<Args>(args)...) {
|
||
if (count) {
|
||
K* str = d().init(count);
|
||
traits::assign(str, count, pad);
|
||
str[count] = 0;
|
||
} else
|
||
d().create_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор из строкового выражения.
|
||
* @param expr - строковое выражение.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @details Конструктор запрашивает у строкового выражения `length()`,
|
||
* выделяет память нужного размера, и вызывает метод `place()` для размещения
|
||
* результата в буфере.
|
||
* @en @brief Constructor from a string expression.
|
||
* @param expr - string expression.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
* @details The constructor queries the string expression `length()`,
|
||
* allocates memory of the required size, and calls the `place()` method to allocate
|
||
* result in buffer.
|
||
*/
|
||
template<typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
constexpr str_storable(const StrExprForType<K> auto& expr, Args&&... args) : Allocator(std::forward<Args>(args)...) {
|
||
size_t len = expr.length();
|
||
if (len)
|
||
*expr.place(d().init(len)) = 0;
|
||
else
|
||
d().create_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор из строкового источника с заменой.
|
||
* @param f - строковый объект, из которого берётся исходная строка.
|
||
* @param pattern - подстрока, которую надо заменить.
|
||
* @param repl - строка, на которую надо заменить.
|
||
* @param offset - начальная позиция для поиска подстрок.
|
||
* @param maxCount - максимальное количество замен, 0 - без ограничений.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Constructor from string source with replacement.
|
||
* @param f - the string object from which the source string is taken.
|
||
* @param pattern - substring to be replaced.
|
||
* @param repl - the string to be replaced with.
|
||
* @param offset - starting position for searching substrings.
|
||
* @param maxCount - maximum number of replacements, 0 - no restrictions.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
str_storable(const From& f, s_str pattern, s_str repl, size_t offset = 0, size_t maxCount = 0, Args&&... args)
|
||
: Allocator(std::forward<Args>(args)...) {
|
||
|
||
auto findes = f.find_all(pattern, offset, maxCount);
|
||
if (!findes.size()) {
|
||
new (this) my_type{f};
|
||
return;
|
||
}
|
||
size_t srcLen = f.length();
|
||
size_t newSize = srcLen + static_cast<ptrdiff_t>(repl.len - pattern.len) * findes.size();
|
||
|
||
if (!newSize) {
|
||
new (this) my_type{};
|
||
return;
|
||
}
|
||
|
||
K* ptr = d().init(newSize);
|
||
const K* src = f.symbols();
|
||
size_t from = 0;
|
||
for (const auto& s: findes) {
|
||
size_t copyLen = s - from;
|
||
if (copyLen) {
|
||
traits::copy(ptr, src + from, copyLen);
|
||
ptr += copyLen;
|
||
}
|
||
if (repl.len) {
|
||
traits::copy(ptr, repl.str, repl.len);
|
||
ptr += repl.len;
|
||
}
|
||
from = s + pattern.len;
|
||
}
|
||
srcLen -= from;
|
||
if (srcLen) {
|
||
traits::copy(ptr, src + from, srcLen);
|
||
ptr += srcLen;
|
||
}
|
||
*ptr = 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор преобразования в нуль-терминированную C-строку.
|
||
* @return const K* - указатель на начало строки.
|
||
* @en @brief Conversion operator to a null-terminated C string.
|
||
* @return const K* - pointer to the beginning of the line.
|
||
*/
|
||
operator const K*() const noexcept {
|
||
return d().symbols();
|
||
}
|
||
/*!
|
||
* @ru @brief Получить simple_str_nt, начиная с заданного символа.
|
||
* @param from - позиция начального символа, по умолчанию 0.
|
||
* @return simple_str_nt,
|
||
* @en @brief Get simple_str_nt starting at the given character.
|
||
* @param from - position of the starting character, default 0.
|
||
* @return simple_str_nt,
|
||
*/
|
||
s_str_nt to_nts(size_t from = 0) const {
|
||
size_t len = d().length();
|
||
if (from >= len) {
|
||
from = len;
|
||
}
|
||
return {d().symbols() + from, len - from};
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать в simple_str_nt.
|
||
* @return simple_str_nt.
|
||
* @en @brief Convert to simple_str_nt.
|
||
* @return simple_str_nt.
|
||
*/
|
||
operator s_str_nt() const {
|
||
return {d().symbols(), d().length()};
|
||
}
|
||
/*!
|
||
* @ru @brief Конкатенация строк из контейнера в одну строку.
|
||
* @param strings - контейнер со строками.
|
||
* @param delimeter - разделитель, добавляемый между строками.
|
||
* @param tail - добавить разделитель после последней строки.
|
||
* @param skip_empty - пропускать пустые строки без добавления разделителя.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @details Функция служит для слияния контейнера строк в одну строку с разделителем.
|
||
* ```cpp
|
||
* std::vector<ssa> strings = get_strings();
|
||
* ssa delim = get_current_delimeter();
|
||
* auto line = lstringa<200>::join(strings, delimeter);
|
||
* ```
|
||
* Стоит отметить, что при заранее известном разделителе лучше пользоваться строковым выражением `e_join`.
|
||
* ```cpp
|
||
* std::vector<ssa> strings = get_strings();
|
||
* lstringa<200> line{e_join(strings, "/")};
|
||
* ```
|
||
* В этом случае компилятор может лучше оптимизировать код слияния строк.
|
||
* @en @brief Concatenate strings from the container into one string.
|
||
* @param strings - container with strings.
|
||
* @param delimeter - delimiter added between lines.
|
||
* @param tail - add a separator after the last line.
|
||
* @param skip_empty - skip empty lines without adding a separator.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
* @details The function is used to merge a container of strings into one delimited string.
|
||
* ```cpp
|
||
* std::vector<ssa> strings = get_strings();
|
||
* ssa delim = get_current_delimeter();
|
||
* auto line = lstringa<200>::join(strings, delimeter);
|
||
* ```
|
||
* It is worth noting that if the separator is known in advance, it is better to use the string expression `e_join`.
|
||
* ```cpp
|
||
* std::vector<ssa> strings = get_strings();
|
||
* lstringa<200> line{e_join(strings, "/")};
|
||
* ```
|
||
* In this case, the compiler can better optimize the string merging code.
|
||
*/
|
||
template<typename T, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type join(const T& strings, s_str delimeter, bool tail = false, bool skip_empty = false, Args&&... args) {
|
||
my_type result(std::forward<Args>(args)...);
|
||
if (strings.size()) {
|
||
if (strings.size() == 1 && (!delimeter.length() || !tail)) {
|
||
result = strings.front();
|
||
} else {
|
||
size_t commonLen = 0;
|
||
for (const auto& t: strings) {
|
||
size_t len = t.length();
|
||
if (len > 0 || !skip_empty) {
|
||
if (commonLen > 0) {
|
||
commonLen += delimeter.len;
|
||
}
|
||
commonLen += len;
|
||
}
|
||
}
|
||
commonLen += (tail && delimeter.len > 0 && (commonLen > 0 || (!skip_empty && strings.size() > 0))? delimeter.len : 0);
|
||
if (commonLen) {
|
||
K* ptr = result.init(commonLen);
|
||
K* write = ptr;
|
||
for (const auto& t: strings) {
|
||
size_t copyLen = t.length();
|
||
if (delimeter.len > 0 && write != ptr && (copyLen || !skip_empty)) {
|
||
ch_traits<K>::copy(write, delimeter.str, delimeter.len);
|
||
write += delimeter.len;
|
||
}
|
||
ch_traits<K>::copy(write, t.symbols(), copyLen);
|
||
write += copyLen;
|
||
}
|
||
if (delimeter.len > 0 && tail && (write != ptr || (!skip_empty && strings.size() > 0))) {
|
||
ch_traits<K>::copy(write, delimeter.str, delimeter.len);
|
||
write += delimeter.len;
|
||
}
|
||
*write = 0;
|
||
} else {
|
||
result.create_empty();
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
/*!
|
||
* @ru @brief Создать строку, копию переданной в верхнем регистре символов ASCII.
|
||
* @param f - строка источник.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Create a string copy of the passed in uppercase ASCII characters.
|
||
* @param f - source string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type uppered_only_ascii_from(const From& f, Args&&... args) {
|
||
return changeCaseAscii(f, makeAsciiUpper<K>, std::forward<Args>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Создать копию переданной строки в нижнем регистре символов ASCII.
|
||
* @param f - строка источник.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Create a copy of the passed string in lowercase ASCII characters.
|
||
* @param f - source string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type lowered_only_ascii_from(const From& f, Args&&... args) {
|
||
return changeCaseAscii(f, makeAsciiLower<K>, std::forward<Args>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Создать копию переданной строки в верхнем регистре символов Unicode первой плоскости (<0xFFFF).
|
||
* @param f - строка источник.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @details Регистр меняется упрощенными таблицами, где один code_point всегда меняется в один code_point
|
||
* (но для UTF-8 возможно, что длина в code unit'ах изменится).
|
||
* @en @brief Create a copy of the passed string in uppercase Unicode characters of the first plane (<0xFFFF).
|
||
* @param f - source string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
* @details Case is changed by simplified tables, where one code_point is always changed to one code_point
|
||
* (but for UTF-8 it is possible that the length in code units will change).
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type uppered_from(const From& f, Args&&... args) {
|
||
return ChangeCase<K>::changeCase(f, uni::upper, std::forward<Args>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Создать копию переданной строки в нижнем регистре символов Unicode первой плоскости (<0xFFFF).
|
||
* @param f - строка источник.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @details Регистр меняется упрощенными таблицами, где один code_point всегда меняется в один code_point
|
||
* (но для UTF-8 возможно, что длина в code unit'ах изменится).
|
||
* @en @brief Create a copy of the passed string in lowercase Unicode characters of the first plane (<0xFFFF).
|
||
* @param f - source string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
* @details Case is changed by simplified tables, where one code_point is always changed to one code_point
|
||
* (but for UTF-8 it is possible that the length in code units will change).
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type lowered_from(const From& f, Args&&... args) {
|
||
return ChangeCase<K>::changeCase(f, uni::lower, std::forward<Args>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Создать копию переданной строки с заменой подстрок.
|
||
* @param f - строка источник.
|
||
* @param pattern - подстрока, которую надо заменить.
|
||
* @param repl - строка, на которую надо заменить.
|
||
* @param offset - начальная позиция для поиска подстрок.
|
||
* @param maxCount - максимальное количество замен, 0 - без ограничений.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Create a copy of the passed string with substrings replaced.
|
||
* @param f - source string.
|
||
* @param pattern - substring to be replaced.
|
||
* @param repl - the string to be replaced with.
|
||
* @param offset - starting position for searching substrings.
|
||
* @param maxCount - maximum number of replacements, 0 - no restrictions.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<StrType<K> From, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
static my_type replaced_from(const From& f, s_str pattern, s_str repl, size_t offset = 0, size_t maxCount = 0, Args&&... args) {
|
||
return my_type{f, pattern, repl, offset, maxCount, std::forward<Args>(args)...};
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Концепт типа, управляющего памятью
|
||
* @en @brief Concept of a memory management type
|
||
*/
|
||
template<typename A>
|
||
concept Allocatorable = requires(A& a, size_t size, void* void_ptr) {
|
||
{ a.allocate(size) } -> std::same_as<void*>;
|
||
{ a.deallocate(void_ptr) } noexcept -> std::same_as<void>;
|
||
};
|
||
|
||
struct printf_selector {
|
||
template<typename K, typename... T> requires (is_one_of_std_char_v<K>)
|
||
static int snprintf(K* buffer, size_t count, const K* format, T&&... args) {
|
||
if constexpr (std::is_same_v<K, u8s>) {
|
||
#ifndef _WIN32
|
||
return std::snprintf(buffer, count, format, std::forward<T>(args)...);
|
||
#else
|
||
// Поддерживает позиционные параметры
|
||
// Supports positional parameters
|
||
return _sprintf_p(buffer, count, format, args...);
|
||
#endif
|
||
} else {
|
||
#ifndef _WIN32
|
||
return std::swprintf(to_one_of_std_char(buffer), count, to_one_of_std_char(format), args...);
|
||
#else
|
||
// Поддерживает позиционные параметры
|
||
// Supports positional parameters
|
||
return _swprintf_p(to_one_of_std_char(buffer), count, to_one_of_std_char(format), args...);
|
||
#endif
|
||
}
|
||
}
|
||
template<typename K> requires (is_one_of_std_char_v<K>)
|
||
static int vsnprintf(K* buffer, size_t count, const K* format, va_list args) {
|
||
if constexpr (std::is_same_v<K, u8s>) {
|
||
#ifndef _WIN32
|
||
return std::vsnprintf(buffer, count, format, args);
|
||
#else
|
||
// Поддерживает позиционные параметры
|
||
// Supports positional parameters
|
||
return _vsprintf_p(buffer, count, format, args);
|
||
#endif
|
||
} else {
|
||
#ifndef _WIN32
|
||
return std::vswprintf(to_one_of_std_char(buffer), count, to_one_of_std_char(format), args);
|
||
#else
|
||
// Поддерживает позиционные параметры
|
||
// Supports positional parameters
|
||
return _vswprintf_p(buffer, count, format, args);
|
||
#endif
|
||
}
|
||
}
|
||
};
|
||
|
||
inline size_t grow2(size_t ret, size_t currentCapacity) {
|
||
return ret <= currentCapacity ? ret : ret * 2;
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Базовый класс работы с изменяемыми строками
|
||
* @tparam K - тип символов
|
||
* @tparam Impl - конечный тип наследника
|
||
* @details По прежнему ничего не знает о том, где наследник хранит строку и её размер.
|
||
* Просто вызывает его методы для получения места, и заполняет его при необходимости.
|
||
* Для работы класс-наследник должен реализовать методы:
|
||
* - `size_t length() const noexcept` - возвращает длину строки
|
||
* - `const K* symbols() const` - возвращает указатель на начало строки
|
||
* - `bool is_empty() const noexcept` - проверка, не пустая ли строка
|
||
* - `K* str() noexcept` - Неконстантный указатель на начало строки
|
||
* - `K* set_size(size_t size)` - Изменить размер строки, как больше, так и меньше.
|
||
* Содержимое строки нужно оставить.
|
||
* - `K* reserve_no_preserve(size_t size)` - выделить место под строку, старую можно не сохранять
|
||
* - `K* alloc_for_copy(size_t size)` - выделить место для копии строки заданного размера, пока не изменяя
|
||
* саму строку, можно вернуть текущий буфер, если место позволяет.
|
||
* - `set_from_copy(K* str, size_t size)` - присвоить строку из памяти, ранее выделенной в alloc_for_copy.
|
||
* Если место выделялось в текущем буфере, ничего не делать.
|
||
* - `size_t capacity() const noexcept` - вернуть текущую ёмкость строки, сколько может поместится без аллокации.
|
||
* @en @brief Base class for working with mutable strings
|
||
* @tparam K - character type
|
||
* @tparam Impl - the final type of the successor
|
||
* @details Still knows nothing about where the heir stores the string and its size.
|
||
* Simply calls its methods to get the space, and fills it as needed.
|
||
* To work, the descendant class must implement the following methods:
|
||
* - `size_t length() const noexcept` - returns the length of the string
|
||
* - `const K* symbols() const` - returns a pointer to the beginning of the line
|
||
* - `bool is_empty() const noexcept` - checks whether the string is empty
|
||
* - `K* str() noexcept` - Non-const pointer to the beginning of the string
|
||
* - `K* set_size(size_t size)` - Change the size of the string, either larger or smaller.
|
||
* The contents of the line must be left.
|
||
* - `K* reserve_no_preserve(size_t size)` - allocate space for a line, you don’t have to save the old one
|
||
* - `K* alloc_for_copy(size_t size)` - allocate space for a copy of a string of a given size, without changing it yet
|
||
* the string itself, you can return the current buffer if space allows.
|
||
* - `set_from_copy(K* str, size_t size)` - assign a string from memory previously allocated in alloc_for_copy.
|
||
* If space was allocated in the current buffer, do nothing.
|
||
* - `size_t capacity() const noexcept` - return the current capacity of the string, as much as can fit without allocation.
|
||
*/
|
||
template<typename K, typename Impl>
|
||
class str_mutable {
|
||
public:
|
||
using my_type = Impl;
|
||
|
||
private:
|
||
Impl& d() {
|
||
return *static_cast<Impl*>(this);
|
||
}
|
||
const Impl& d() const {
|
||
return *static_cast<const Impl*>(this);
|
||
}
|
||
size_t _len() const noexcept {
|
||
return d().length();
|
||
}
|
||
const K* _str() const noexcept {
|
||
return d().symbols();
|
||
}
|
||
using str_piece = simple_str<K>;
|
||
using symb_type = K;
|
||
using traits = ch_traits<K>;
|
||
using uni = unicode_traits<K>;
|
||
using uns_type = std::make_unsigned_t<K>;
|
||
|
||
template<typename Op>
|
||
Impl& make_trim_op(const Op& op) {
|
||
str_piece me = static_cast<str_piece>(d()), pos = op(me);
|
||
if (me.length() != pos.length()) {
|
||
if (me.symbols() != pos.symbols())
|
||
traits::move(const_cast<K*>(me.symbols()), pos.symbols(), pos.length());
|
||
d().set_size(pos.length());
|
||
}
|
||
return d();
|
||
}
|
||
|
||
template<auto Op>
|
||
Impl& commonChangeCase() {
|
||
size_t len = _len();
|
||
if (len)
|
||
Op(_str(), len, str());
|
||
return d();
|
||
}
|
||
// GCC до сих пор не позволяет делать внутри класса полную специализацию вложенного класса,
|
||
// только частичную. Поэтому добавим неиспользуемый параметр шаблона.
|
||
// GCC still does not allow full specialization of a nested class within a class,
|
||
// only partial. Resources additive unused parameter template.
|
||
template<typename T, bool Dummy = true>
|
||
struct CaseTraits {
|
||
static Impl& upper(Impl& obj) {
|
||
return obj.template commonChangeCase<unicode_traits<K>::upper>();
|
||
}
|
||
static Impl& lower(Impl& obj) {
|
||
return obj.template commonChangeCase<unicode_traits<K>::lower>();
|
||
}
|
||
};
|
||
|
||
template<auto Op>
|
||
Impl& utf8CaseChange() {
|
||
// Для utf-8 такая операция может изменить длину строки, поэтому для них делаем разные специализации
|
||
// For utf-8, such an operation can change the length of the string, so we make different specializations for them
|
||
size_t len = _len();
|
||
if (len) {
|
||
u8s* writePos = str();
|
||
const u8s *startData = writePos, *readPos = writePos;
|
||
size_t newLen = Op(readPos, len, writePos, len);
|
||
if (newLen < len) {
|
||
// Строка просто укоротилась
|
||
// The string was simply shortened
|
||
d().set_size(newLen);
|
||
} else if (newLen > len) {
|
||
// Строка не влезла в буфер.
|
||
// The line did not fit into the buffer.
|
||
size_t readed = static_cast<size_t>(readPos - startData);
|
||
size_t writed = static_cast<size_t>(writePos - startData);
|
||
d().set_size(newLen);
|
||
startData = str(); // при изменении размера могло изменится | may change when resizing
|
||
readPos = startData + readed;
|
||
writePos = const_cast<u8s*>(startData) + writed;
|
||
Op(readPos, len - readed, writePos, newLen - writed);
|
||
}
|
||
}
|
||
return d();
|
||
}
|
||
template<bool Dummy>
|
||
struct CaseTraits<u8s, Dummy> {
|
||
static Impl& upper(Impl& obj) {
|
||
return obj.template utf8CaseChange<unicode_traits<u8s>::upper>();
|
||
}
|
||
static Impl& lower(Impl& obj) {
|
||
return obj.template utf8CaseChange<unicode_traits<u8s>::lower>();
|
||
}
|
||
};
|
||
|
||
template<TrimSides S, bool withSpaces, typename T, size_t N = const_lit_for<K, T>::Count>
|
||
Impl& makeTrim(T&& pattern) {
|
||
return make_trim_op(trim_operator<S, K, N - 1, withSpaces>{pattern});
|
||
}
|
||
|
||
template<TrimSides S, bool withSpaces>
|
||
Impl& makeTrim(str_piece pattern) {
|
||
return make_trim_op(trim_operator<S, K, 0, withSpaces>{{pattern}});
|
||
}
|
||
|
||
public:
|
||
/*!
|
||
* @ru @brief Получить указатель на буфер строки.
|
||
* @return K* - указатель на буфер строки.
|
||
* @en @brief Get a pointer to the string buffer.
|
||
* @return K* - pointer to the string buffer.
|
||
*/
|
||
K* str() noexcept {
|
||
return d().str();
|
||
}
|
||
/*!
|
||
* @ru @brief Получить указатель на буфер строки.
|
||
* @return K* - указатель на буфер строки.
|
||
* @en @brief Get a pointer to the string buffer.
|
||
* @return K* - pointer to the string buffer.
|
||
*/
|
||
explicit operator K*() noexcept {
|
||
return str();
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить пробельные символы в начале и в конце строки.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove whitespace from the beginning and end of a line.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim() {
|
||
return make_trim_op(SimpleTrim<TrimSides::TrimAll, K>{});
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить пробельные символы в начале строки.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove whitespace at the beginning of a line.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_left() {
|
||
return make_trim_op(SimpleTrim<TrimSides::TrimLeft, K>{});
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить пробельные символы в конце строки.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove whitespace from the end of a line.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_right() {
|
||
return make_trim_op(SimpleTrim<TrimSides::TrimRight, K>{});
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, в начале и в конце строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove characters included in a string literal at the beginning and end of the line.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimAll, false>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, в начале строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove characters included in a string literal at the beginning of the line.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim_left(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimLeft, false>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, в конце строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove characters included in a string literal at the end of the line.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim_right(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimRight, false>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, а также пробельные символы, в начале и в конце строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in a string literal, as well as whitespace, at the beginning and end of the string.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim_with_spaces(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimAll, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, а также пробельные символы, в начале строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in a string literal, as well as whitespace, at the beginning of a line.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @en @brief* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim_left_with_spaces(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimLeft, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в строковый литерал, а также пробельные символы, в конце строки.
|
||
* @param pattern - строковый литерал, содержащий символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in a string literal, as well as whitespace, at the end of a string.
|
||
* @param pattern is a string literal containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
requires is_const_pattern<N>
|
||
Impl& trim_right_with_wpaces(T&& pattern) {
|
||
return makeTrim<TrimSides::TrimRight, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, в начале и в конце строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string at the beginning and end of the line.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim(str_piece pattern) {
|
||
return pattern.length() ? makeTrim<TrimSides::TrimAll, false>(pattern) : d();
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, в начале строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string at the beginning of the line.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_left(str_piece pattern) {
|
||
return pattern.length() ? makeTrim<TrimSides::TrimLeft, false>(pattern) : d();
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, в конце строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string from the end of the string.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_right(str_piece pattern) {
|
||
return pattern.length() ? makeTrim<TrimSides::TrimRight, false>(pattern) : d();
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, а также пробельные символы, в начале и в конце строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string, as well as whitespace characters, at the beginning and end of the string.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_with_spaces(str_piece pattern) {
|
||
return makeTrim<TrimSides::TrimAll, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, а также пробельные символы, в начале строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string, as well as whitespace, at the beginning of the string.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_left_with_spaces(str_piece pattern) {
|
||
return makeTrim<TrimSides::TrimLeft, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить символы, входящие в переданную строку, а также пробельные символы, в конце строки.
|
||
* @param pattern - строка, содержащая символы, которые надо удалить.
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Remove characters included in the passed string, as well as whitespace at the end of the string.
|
||
* @param pattern - a string containing the characters to be removed.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& trim_right_with_spaces(str_piece pattern) {
|
||
return makeTrim<TrimSides::TrimRight, true>(pattern);
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать в верхний регистр ASCII символы.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Convert ASCII characters to uppercase.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& upper_only_ascii() {
|
||
K* ptr = str();
|
||
for (size_t i = 0, l = _len(); i < l; i++, ptr++) {
|
||
K s = *ptr;
|
||
if (isAsciiLower(s))
|
||
*ptr = s & ~0x20;
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать в нижний регистр ASCII символы.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Convert ASCII characters to lowercase.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& lower_only_ascii() {
|
||
K* ptr = str();
|
||
for (size_t i = 0, l = _len(); i < l; i++, ptr++) {
|
||
K s = *ptr;
|
||
if (isAsciiUpper(s))
|
||
*ptr = s | 0x20;
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать в верхний регистр Unicode символы первой плоскости (<0xFFFF).
|
||
* @details Регистр меняется упрощенными таблицами, где один code_point всегда меняется в один code_point
|
||
* (но для UTF-8 возможно, что длина в code unit'ах изменится).
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Convert first plane characters (<0xFFFF) to uppercase Unicode.
|
||
* @details Case is changed by simplified tables, where one code_point is always changed to one code_point
|
||
* (but for UTF-8 it is possible that the length in code units will change).
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& upper() {
|
||
// Для utf-8 такая операция может изменить длину строки, поэтому для них делаем разные специализации
|
||
// For utf-8, such an operation can change the length of the string, so we make different specializations for them
|
||
return CaseTraits<K>::upper(d());
|
||
}
|
||
/*!
|
||
* @ru @brief Преобразовать в нижний регистр Unicode символы первой плоскости (<0xFFFF).
|
||
* @details Регистр меняется упрощенными таблицами, где один code_point всегда меняется в один code_point
|
||
* (но для UTF-8 возможно, что длина в code unit'ах изменится).
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Convert first plane characters (<0xFFFF) to lowercase Unicode.
|
||
* @details Case is changed by simplified tables, where one code_point is always changed to one code_point
|
||
* (but for UTF-8 it is possible that the length in code units will change).
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& lower() {
|
||
// Для utf-8 такая операция может изменить длину строки, поэтому для них делаем разные специализации
|
||
// For utf-8, such an operation can change the length of the string, so we make different specializations for them
|
||
return CaseTraits<K>::lower(d());
|
||
}
|
||
|
||
private:
|
||
template<typename T>
|
||
Impl& changeImpl(size_t from, size_t len, T expr) {
|
||
size_t myLen = _len();
|
||
if (from > myLen) {
|
||
from = myLen;
|
||
}
|
||
if (from + len > myLen) {
|
||
len = myLen - from;
|
||
}
|
||
K* buffer = str();
|
||
size_t otherLen = expr.length();
|
||
if (len == otherLen) {
|
||
expr.place(buffer + from);
|
||
} else {
|
||
size_t tailLen = myLen - from - len;
|
||
if (len > otherLen) {
|
||
expr.place(buffer + from);
|
||
traits::move(buffer + from + otherLen, buffer + from + len, tailLen);
|
||
d().set_size(myLen - (len - otherLen));
|
||
} else {
|
||
buffer = d().set_size(myLen + otherLen - len);
|
||
traits::move(buffer + from + otherLen, buffer + from + len, tailLen);
|
||
expr.place(buffer + from);
|
||
}
|
||
}
|
||
return d();
|
||
}
|
||
|
||
template<typename T>
|
||
Impl& appendImpl(T expr) {
|
||
if (size_t len = expr.length(); len) {
|
||
size_t size = _len();
|
||
expr.place(d().set_size(size + len) + size);
|
||
}
|
||
return d();
|
||
}
|
||
|
||
template<typename T>
|
||
Impl& appendFromImpl(size_t pos, T expr) {
|
||
if (pos > _len())
|
||
pos = _len();
|
||
if (size_t len = expr.length())
|
||
expr.place(d().set_size(pos + len) + pos);
|
||
else
|
||
d().set_size(pos);
|
||
return d();
|
||
}
|
||
|
||
public:
|
||
inline static constexpr bool is_str_mutable = true;
|
||
/*!
|
||
* @ru @brief Добавить другую строку в конец строки.
|
||
* @param other - другая строка.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add another line to the end of the line.
|
||
* @param other - another string.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& append(str_piece other) {
|
||
return appendImpl<str_piece>(other);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить строковое выражение в конец строки.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add a string expression to the end of the line.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& append(const A& expr) {
|
||
return appendImpl<const A&>(expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить другую строку в конец строки.
|
||
* @param other - другая строка.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add another line to the end of the line.
|
||
* @param other - another line.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& operator+=(str_piece other) {
|
||
return appendImpl<str_piece>(other);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить строковое выражение в конец строки.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add a string expression to the end of the line.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& operator+=(const A& expr) {
|
||
return appendImpl<const A&>(expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить другую строку, начиная с заданной позиции.
|
||
* @param pos - позиция, с которой добавлять. Сначала строка укорачивается до заданного
|
||
* размера, а потом добавляется другая строка.
|
||
* @param other - другая строка.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details Если строка длиинее`pos`, то она укорачивается до этого размера, а потом добавляется `other`.
|
||
* @en @brief Add another line starting at the given position.
|
||
* @param pos - the position from which to add. First, the string is shortened to the specified value
|
||
* size, and then another line is added.
|
||
* @param other - another string.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details If the string is longer than `pos`, then it is shortened to this size, and then `other` is added.
|
||
*/
|
||
Impl& append_in(size_t pos, str_piece other) {
|
||
return appendFromImpl<str_piece>(pos, other);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить строковое выражение, начиная с заданной позиции.
|
||
* @param pos - позиция, с которой добавлять. Сначала строка укорачивается до заданного
|
||
* размера, а потом добавляется строковое выражение.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details Если строка длиннее`pos`, то она укорачивается до этого размера, а потом добавляется `expr`.
|
||
* @en @brief Add a string expression starting at the given position.
|
||
* @param pos - the position from which to add. First, the string is shortened to the specified value
|
||
* size, and then a string expression is added.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details If the string is longer than `pos`, then it is shortened to this size, and then `expr` is added.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& append_in(size_t pos, const A& expr) {
|
||
return appendFromImpl<const A&>(pos, expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Заменить кусок строки на другую строку.
|
||
* @param from - начальная позиция для замены.
|
||
* @param len - длина заменяемой части.
|
||
* @param other - строка, на которую эта часть меняется .
|
||
* @return Impl& - ссылку на себя же
|
||
* @en @brief Replace a piece of string with another string.
|
||
* @param from - starting position for replacement.
|
||
* @param len - length of the part to be replaced.
|
||
* @param other - the string this part is changed to.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& change(size_t from, size_t len, str_piece other) {
|
||
return changeImpl<str_piece>(from, len, other);
|
||
}
|
||
/*!
|
||
* @ru @brief Заменить кусок строки на строковое выражение.
|
||
* @param from - начальная позиция для замены.
|
||
* @param len - длина заменяемой части.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Replace a piece of string with a string expression.
|
||
* @param from - starting position for replacement.
|
||
* @param len - length of the part to be replaced.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& change(size_t from, size_t len, const A& expr) {
|
||
return changeImpl<const A&>(from, len, expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Вставить строку в указанную позицию.
|
||
* @param to - позиция для вставки.
|
||
* @param other - вставляемая строка.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Insert a line at the specified position.
|
||
* @param to - insertion position.
|
||
* @param other - the string to be inserted.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& insert(size_t to, str_piece other) {
|
||
return changeImpl<str_piece>(to, 0, other);
|
||
}
|
||
/*!
|
||
* @ru @brief Вставить строковое выражение в указанную позицию.
|
||
* @param to - позиция для вставки.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Insert a string expression at the specified position.
|
||
* @param to - insertion position.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& insert(size_t to, const A& expr) {
|
||
return changeImpl<const A&>(to, 0, expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Удалить часть строки.
|
||
* @param from - позиция, с которой удалить.
|
||
* @param len - длина удаляемой части.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Remove part of a line.
|
||
* @param from - the position from which to delete.
|
||
* @param len - length of the part to be deleted.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& remove(size_t from, size_t len) {
|
||
return changeImpl<const empty_expr<K>&>(from, len, {});
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить другую строку в начало строки.
|
||
* @param other - другая строка.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add another line to the beginning of the line.
|
||
* @param other - another string.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& prepend(str_piece other) {
|
||
return changeImpl<str_piece>(0, 0, other);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавить строковое выражение в начало строки.
|
||
* @param expr - строковое выражение.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Add a string expression to the beginning of a line.
|
||
* @param expr - string expression.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrExprForType<K> A>
|
||
Impl& prepend(const A& expr) {
|
||
return changeImpl<const A&>(0, 0, expr);
|
||
}
|
||
/*!
|
||
* @ru @brief Заменить вхождения подстроки на другую строку.
|
||
* @param pattern - искомая подстрока.
|
||
* @param repl - строка замены.
|
||
* @param offset - начальная позиция для поиска.
|
||
* @param maxCount - максимальное количество замен, 0 - без ограничений.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Replace occurrences of a substring with another string.
|
||
* @param pattern - the substring to search for.
|
||
* @param repl - replacement string.
|
||
* @param offset - the starting position for the search.
|
||
* @param maxCount - maximum number of replacements, 0 - no restrictions.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
Impl& replace(str_piece pattern, str_piece repl, size_t offset = 0, size_t maxCount = 0) {
|
||
offset = d().find(pattern, offset);
|
||
if (offset == str::npos) {
|
||
return d();
|
||
}
|
||
if (!maxCount)
|
||
maxCount--;
|
||
size_t replLength = repl.length(), patternLength = pattern.length();
|
||
|
||
if (patternLength == replLength) {
|
||
// Заменяем inplace на подстроку такой же длины
|
||
// Replace inplace with a substring of the same length
|
||
K* ptr = str();
|
||
for (size_t i = 0; i < maxCount; i++) {
|
||
traits::copy(ptr + offset, repl.symbols(), replLength);
|
||
offset = d().find(pattern, offset + replLength);// replLength == patternLength
|
||
if (offset == str::npos)
|
||
break;
|
||
}
|
||
} else if (patternLength > replLength) {
|
||
// Заменяем на более короткий кусок, длина текста уменьшится, идём слева направо
|
||
// Replace with a shorter piece, the length of the text will decrease, go from left to right
|
||
K* ptr = str();
|
||
traits::copy(ptr + offset, repl.symbols(), replLength);
|
||
size_t posWrite = offset + replLength;
|
||
maxCount--;
|
||
offset += patternLength;
|
||
|
||
for (size_t i = 0; i < maxCount; i++) {
|
||
size_t idx = d().find(pattern, offset);
|
||
if (idx == str::npos)
|
||
break;
|
||
size_t lenOfPiece = idx - offset;
|
||
traits::move(ptr + posWrite, ptr + offset, lenOfPiece);
|
||
posWrite += lenOfPiece;
|
||
traits::copy(ptr + posWrite, repl.symbols(), replLength);
|
||
posWrite += replLength;
|
||
offset = idx + patternLength;
|
||
}
|
||
size_t tailLen = _len() - offset;
|
||
traits::move(ptr + posWrite, ptr + offset, tailLen);
|
||
d().set_size(posWrite + tailLen);
|
||
} else {
|
||
struct replace_grow_helper {
|
||
replace_grow_helper(my_type& src, str_piece p, str_piece r, size_t mc, size_t d)
|
||
: source(src), pattern(p), repl(r), maxCount(mc), delta(d) {}
|
||
my_type& source;
|
||
const str_piece pattern;
|
||
const str_piece repl;
|
||
size_t maxCount;
|
||
const size_t delta;
|
||
size_t all_delta{};
|
||
K* reserve_for_copy{};
|
||
size_t end_of_piece{};
|
||
size_t total_length{};
|
||
|
||
void replace(size_t offset) {
|
||
size_t finded[16] = {source.find(pattern, offset)};
|
||
if (finded[0] == str::npos) {
|
||
return;
|
||
}
|
||
maxCount--;
|
||
offset = finded[0] + pattern.length();
|
||
all_delta += delta;
|
||
size_t idx = 1;
|
||
for (size_t end = std::min(maxCount, std::size(finded)); idx < end; idx++, maxCount--) {
|
||
finded[idx] = source.find(pattern, offset);
|
||
if (finded[idx] == str::npos) {
|
||
break;
|
||
}
|
||
offset = finded[idx] + pattern.length();
|
||
all_delta += delta;
|
||
}
|
||
bool needMore = maxCount > 0 && idx == std::size(finded) && offset < source.length() - pattern.length();
|
||
if (needMore) {
|
||
replace(offset); // здесь произведутся замены в оставшемся хвосте | replacements will be made here in the remaining tail
|
||
}
|
||
// Теперь делаем свои замены
|
||
// Now we make our replacements
|
||
if (!reserve_for_copy) {
|
||
// Только начинаем
|
||
// Just getting started
|
||
end_of_piece = source.length();
|
||
total_length = end_of_piece + all_delta;
|
||
reserve_for_copy = source.alloc_for_copy(total_length);
|
||
}
|
||
K* dst_start = reserve_for_copy;
|
||
const K* src_start = source.symbols();
|
||
while(idx-- > 0) {
|
||
size_t pos = finded[idx] + pattern.length();
|
||
size_t lenOfPiece = end_of_piece - pos;
|
||
ch_traits<K>::move(dst_start + pos + all_delta, src_start + pos, lenOfPiece);
|
||
ch_traits<K>::copy(dst_start + pos + all_delta - repl.length(), repl.symbols(), repl.length());
|
||
all_delta -= delta;
|
||
end_of_piece = finded[idx];
|
||
}
|
||
if (!all_delta && reserve_for_copy != src_start) {
|
||
ch_traits<K>::copy(dst_start, src_start, finded[0]);
|
||
}
|
||
}
|
||
} helper(d(), pattern, repl, maxCount, repl.length() - pattern.length());
|
||
helper.replace(offset);
|
||
d().set_from_copy(helper.reserve_for_copy, helper.total_length);
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Скопировать строку-источник, заменив вхождения подстрок на другую строку.
|
||
* @param f - строка-источник.
|
||
* @param pattern - искомая подстрока.
|
||
* @param repl - строка замены.
|
||
* @param offset - начальная позиция для поиска.
|
||
* @param maxCount - максимальное количество замен, 0 - без ограничений.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Copy the source string, replacing occurrences of substrings with another string.
|
||
* @param f - source string.
|
||
* @param pattern - the substring to search for.
|
||
* @param repl - replacement string.
|
||
* @param offset - the starting position for the search.
|
||
* @param maxCount - maximum number of replacements, 0 - no restrictions.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<StrType<K> From>
|
||
Impl& replace_from(const From& f, str_piece pattern, str_piece repl, size_t offset = 0, size_t maxCount = 0) {
|
||
if (pattern.length() >= repl.length()) {
|
||
K* dst = d().reserve_no_preserve(f.length());
|
||
const K* src = f.symbols();
|
||
size_t delta = 0;
|
||
if (maxCount == 0) {
|
||
maxCount--;
|
||
}
|
||
size_t src_length = f.length(), start = 0;
|
||
while (maxCount--) {
|
||
offset = f.find(pattern, offset);
|
||
if (offset == str::npos) {
|
||
break;
|
||
}
|
||
size_t piece_len = offset - start;
|
||
if (piece_len) {
|
||
ch_traits<K>::copy(dst, src + start, piece_len);
|
||
dst += piece_len;
|
||
}
|
||
if (repl.length()) {
|
||
ch_traits<K>::copy(dst, repl.symbols(), repl.length());
|
||
dst += repl.length();
|
||
}
|
||
delta += pattern.length() - repl.length();
|
||
offset += pattern.length();
|
||
start = offset;
|
||
}
|
||
if (start < src_length) {
|
||
ch_traits<K>::copy(dst, src + start, src_length - start);
|
||
}
|
||
d().set_size(src_length - delta);
|
||
} else {
|
||
d() = f;
|
||
replace(pattern, repl, offset, maxCount);
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Заполнение буфера строки с помощью функтора.
|
||
* @param from - начальная позиция для заполнения.
|
||
* @param fillFunction - size_t(K*, size_t) функтор, получающий адрес буфера строки и его ёмкость,
|
||
* возвращающий необходимый размер строки.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details Функция вызывает функтор, передавая ему адрес буфера строки и его ёмкость.
|
||
* Функтор может изменять буфер в пределах выделенной ёмкости, и должен вернуть размер итоговой строки.
|
||
* Пока возвращаемый размер больше ёмкости (т.е. строка не может поместиться в буфер),
|
||
* выделятся память как минимум возвращенного размера, и функтор вызывается снова.
|
||
* До тех пор, пока возвращённый размер не будет помещаться в буфер строки.
|
||
* Этот размер и становится длиной строки.
|
||
* @en @brief Fill a string buffer using a functor.
|
||
* @param from - starting position to fill.
|
||
* @param fillFunction - size_t(K*, size_t) functor that receives the address of the string buffer and its capacity,
|
||
* returning the required string size.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details The function calls the functor, passing it the address of the string buffer and its capacity.
|
||
* The functor can modify the buffer within the allocated capacity, and must return the size of the resulting string.
|
||
* As long as the returned size is larger than capacity (i.e. the string cannot fit into the buffer),
|
||
* memory of at least the returned size is allocated and the functor is called again.
|
||
* Until the returned size fits into the string buffer.
|
||
* This size becomes the length of the line.
|
||
*/
|
||
template<typename Op>
|
||
Impl& fill(size_t from, const Op& fillFunction) {
|
||
size_t size = _len();
|
||
if (from > size)
|
||
from = size;
|
||
size_t capacity = d().capacity();
|
||
K* ptr = str();
|
||
capacity -= from;
|
||
for (;;) {
|
||
size_t needSize = (size_t)fillFunction(ptr + from, capacity);
|
||
if (capacity >= needSize) {
|
||
d().set_size(from + needSize);
|
||
break;
|
||
}
|
||
ptr = from == 0 ? d().reserve_no_preserve(needSize) : d().set_size(from + needSize);
|
||
capacity = d().capacity() - from;
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Заполняет строку методом fill с нулевой позиции.
|
||
* @param fillFunction - функтор заполнения строки, size_t(K*, size_t).
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Fills a string with the fill method from position zero.
|
||
* @param fillFunction - string filling functor, size_t(K*, size_t).
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename Op>
|
||
requires std::is_invocable_v<Op, K*, size_t>
|
||
Impl& operator<<(const Op& fillFunction) {
|
||
return fill(0, fillFunction);
|
||
}
|
||
/*!
|
||
* @ru @brief Заполняет строку методом fill после конца строки.
|
||
* @param fillFunction - функтор заполнения строки, size_t(K*, size_t).
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Fills a string with the fill method after the end of the string.
|
||
* @param fillFunction - string filling functor, size_t(K*, size_t).
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename Op>
|
||
requires std::is_invocable_v<Op, K*, size_t>
|
||
Impl& operator<<=(const Op& fillFunction) {
|
||
return fill(_len(), fillFunction);
|
||
}
|
||
/*!
|
||
* @ru @brief Вызывает переданный функтор, передав ссылку на себя.
|
||
* @param fillFunction - фуктор void(my_type&).
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Calls the passed functor, passing a reference to itself.
|
||
* @param fillFunction - фуктор void(my_type&).
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename Op>
|
||
requires std::is_invocable_v<Op, my_type&>
|
||
Impl& operator<<(const Op& fillFunction) {
|
||
fillFunction(d());
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью sprintf вывод, начиная с указанной позиции.
|
||
* @param from - начальная позиция добавления.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для sprintf.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends sprintf formatted output starting at the specified position.
|
||
* @param from - starting position of adding.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for sprintf.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& printf_from(size_t from, const K* format, T&&... args) {
|
||
size_t size = _len();
|
||
if (from > size)
|
||
from = size;
|
||
size_t capacity = d().capacity();
|
||
K* ptr = str();
|
||
capacity -= from;
|
||
|
||
int result = 0;
|
||
// Тут грязный хак для u8s и wide_char. u8s версия snprintf сразу возвращает размер нужного буфера, если он мал
|
||
// а swprintf - возвращает -1. Под windows оба варианта xxx_p - тоже возвращают -1.
|
||
// Поэтому для них надо тупо увеличивать буфер наугад, пока не подойдет
|
||
// Here's a dirty hack for u8s and wide_char. u8s version of snprintf immediately returns the size of the required buffer if it is small
|
||
// and swprintf returns -1. Under Windows, both options xxx_p also return -1.
|
||
// Therefore, for them you need to stupidly increase the buffer at random until it fits
|
||
if constexpr (sizeof(K) == 1 && !isWindowsOs) {
|
||
result = printf_selector::snprintf(ptr + from, capacity + 1, format, std::forward<T>(args)...);
|
||
if (result > (int)capacity) {
|
||
ptr = from == 0 ? d().reserve_no_preserve(result) : d().set_size(from + result);
|
||
result = printf_selector::snprintf(ptr + from, result + 1, format, std::forward<T>(args)...);
|
||
}
|
||
} else {
|
||
for (;;) {
|
||
result = printf_selector::snprintf(ptr + from, capacity + 1, format, std::forward<T>(args)...);
|
||
if (result < 0) {
|
||
// Не хватило буфера или ошибка конвертации.
|
||
// Попробуем увеличить буфер в два раза
|
||
// Not enough buffer or conversion error.
|
||
// Let's try to double the buffer
|
||
capacity *= 2;
|
||
ptr = from == 0 ? d().reserve_no_preserve(capacity) : d().set_size(from + capacity);
|
||
} else
|
||
break;
|
||
}
|
||
}
|
||
if (result < 0)
|
||
d().set_size(static_cast<size_t>(traits::length(_str())));
|
||
else
|
||
d().set_size(from + result);
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Форматирует строку помощью sprintf.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для sprintf.
|
||
* @return Impl& - ссылку на себя же
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Formats a string using sprintf.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for sprintf.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& printf(const K* format, T&&... args) {
|
||
return printf_from(0, format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью sprintf вывод в конец строки.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для sprintf.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends sprintf formatted output to the end of the line.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for sprintf.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the row buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& append_printf(const K* format, T&&... args) {
|
||
return printf_from(_len(), format, std::forward<T>(args)...);
|
||
}
|
||
|
||
struct writer {
|
||
my_type* store;
|
||
K* ptr;
|
||
const K* end;
|
||
size_t max_write;
|
||
size_t writed = 0;
|
||
inline static K pad;
|
||
K& operator*() const {
|
||
return *ptr;
|
||
}
|
||
writer& operator++() {
|
||
if (writed < max_write) {
|
||
++ptr;
|
||
if (ptr == end) {
|
||
size_t l = ptr - store->begin();
|
||
store->set_size(l);
|
||
ptr = store->set_size(l + std::min(l / 2, size_t(8192))) + l;
|
||
end = store->end();
|
||
}
|
||
} else {
|
||
ptr = &pad;
|
||
}
|
||
return *this;
|
||
}
|
||
writer operator++(int) {
|
||
auto ret = *this;
|
||
operator++();
|
||
return ret;
|
||
}
|
||
|
||
writer(my_type& s, K* p, K* e, size_t ml) : store(&s), ptr(p), end(e), max_write(ml) {}
|
||
writer() = default;
|
||
writer(const writer&) = delete;
|
||
writer& operator=(const writer&) noexcept = delete;
|
||
writer(writer&&) noexcept = default;
|
||
writer& operator=(writer&&) noexcept = default;
|
||
using difference_type = int;
|
||
};
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью std::format вывод, начиная с указанной позиции.
|
||
* @param from - начальная позиция добавления.
|
||
* @param format - форматная строка, константная.
|
||
* @param ...args - аргументы для std::format.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends std::format-formatted output starting at the specified position.
|
||
* @param from - starting position of adding.
|
||
* @param format - format string, constant.
|
||
* @param ...args - arguments for std::format.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& format_from(size_t from, const FmtString<K, T...>& format, T&&... args) {
|
||
size_t size = _len();
|
||
if (from > size)
|
||
from = size;
|
||
size_t capacity = d().capacity();
|
||
K* ptr = str();
|
||
|
||
auto result = std::format_to(writer{d(), ptr + from, ptr + capacity, size_t(-1)},
|
||
std::forward<decltype(format)>(format), std::forward<T>(args)...);
|
||
d().set_size(result.ptr - _str());
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью std::vformat вывод, начиная с указанной позиции.
|
||
* @param from - начальная позиция добавления.
|
||
* @param max_write - максимальное количество записываемых символов.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для std::vformat.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends std::vformat formatted output starting at the specified position.
|
||
* @param from - starting position of adding.
|
||
* @param max_write - the maximum number of characters to write.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for std::vformat.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& vformat_from(size_t from, size_t max_write, str_piece format, T&&... args) {
|
||
size_t size = _len();
|
||
if (from > size)
|
||
from = size;
|
||
size_t capacity = d().capacity();
|
||
K* ptr = str();
|
||
|
||
if constexpr (std::is_same_v<K, u8s>) {
|
||
auto result = std::vformat_to(
|
||
writer{d(), ptr + from, ptr + capacity, max_write},
|
||
std::basic_string_view<K>{format.symbols(), format.length()},
|
||
std::make_format_args(args...));
|
||
d().set_size(result.ptr - _str());
|
||
} else {
|
||
auto result = std::vformat_to(
|
||
writer{d(), to_one_of_std_char(ptr + from), ptr + capacity, max_write},
|
||
std::basic_string_view<wchar_t>{to_one_of_std_char(format.symbols()), format.length()},
|
||
std::make_wformat_args(std::forward<T>(args)...));
|
||
d().set_size(result.ptr - _str());
|
||
}
|
||
return d();
|
||
}
|
||
/*!
|
||
* @ru @brief Форматирует строку с помощью std::format.
|
||
* @param pattern - форматная строка, константная.
|
||
* @param ...args - аргументы для std::format.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Formats a string using std::format.
|
||
* @param pattern - format string, constant.
|
||
* @param ...args - arguments for std::format.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& format(const FmtString<K, T...>& pattern, T&&... args) {
|
||
return format_from(0, pattern, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью std::format вывод в конец строки.
|
||
* @param format - форматная строка, константная.
|
||
* @param ...args - аргументы для std::format.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends std::format-formatted output to the end of the line.
|
||
* @param format - format string, constant.
|
||
* @param ...args - arguments for std::format.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& append_formatted(const FmtString<K, T...>& format, T&&... args) {
|
||
return format_from(_len(), format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Форматирует строку с помощью std::vformat.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для std::vformat.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Formats a string using std::vformat.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for std::vformat.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& vformat(str_piece format, T&&... args) {
|
||
return vformat_from(0, -1, format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью std::vformat вывод в конец строки.
|
||
* @param format - форматная строка.
|
||
* @param ... - аргументы для std::vformat.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends std::vformat-formatted output to the end of the line.
|
||
* @param format - format string.
|
||
* @param ... - arguments for std::vformat.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& append_vformatted(str_piece format, T&&... args) {
|
||
return vformat_from(_len(), -1, format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Форматирует строку с помощью std::vformat не более указанного размера.
|
||
* @param max_write - максимальное количество записываемых символов.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для std::vformat.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Formats a string using std::vformat up to the specified size.
|
||
* @param max_write - the maximum number of characters to write.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for std::vformat.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& vformat_n(size_t max_write, str_piece format, T&&... args) {
|
||
return vformat_from(0, max_write, format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Добавляет отформатированный с помощью std::vformat вывод в конец строки, записывая не более указанного количества символов.
|
||
* @param max_write - максимальное количество записываемых символов.
|
||
* @param format - форматная строка.
|
||
* @param ...args - аргументы для std::vformat.
|
||
* @return Impl& - ссылку на себя же
|
||
* @details При необходимости автоматически увеличивает размер буфера строки.
|
||
* @en @brief Appends std::vformat-formatted output to the end of the line, writing no more than the specified number of characters.
|
||
* @param max_write - the maximum number of characters to write.
|
||
* @param format - format string.
|
||
* @param ...args - arguments for std::vformat.
|
||
* @return Impl& - a reference to yourself.
|
||
* @details Automatically increases the string buffer size if necessary.
|
||
*/
|
||
template<typename... T> requires (is_one_of_std_char_v<K>)
|
||
Impl& append_vformatted_n(size_t max_write, str_piece format, T&&... args) {
|
||
return vformat_from(_len(), max_write, format, std::forward<T>(args)...);
|
||
}
|
||
/*!
|
||
* @ru @brief Вызов функтора со строкой и переданными аргументами.
|
||
* @param fillFunction - функтор, принимающий первым параметром ссылку на строку.
|
||
* @param ...args - аргументы, передаваемые в функтор.
|
||
* @return Impl& - ссылку на себя же.
|
||
* @en @brief Call a functor with a string and passed arguments.
|
||
* @param fillFunction - a functor that takes a string reference as its first parameter.
|
||
* @param ...args - arguments passed to the functor.
|
||
* @return Impl& - a reference to yourself.
|
||
*/
|
||
template<typename Op, typename... Args>
|
||
Impl& with(const Op& fillFunction, Args&&... args) {
|
||
fillFunction(d(), std::forward<Args>(args)...);
|
||
return d();
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct SharedStringData {
|
||
std::atomic_size_t ref_; // Счетчик ссылок | Reference count
|
||
|
||
SharedStringData() {
|
||
ref_ = 1;
|
||
}
|
||
K* str() const {
|
||
return (K*)(this + 1);
|
||
}
|
||
void incr() {
|
||
ref_.fetch_add(1, std::memory_order_relaxed);
|
||
}
|
||
void decr(Allocatorable auto& allocator) {
|
||
size_t val = ref_.fetch_sub(1, std::memory_order_relaxed);
|
||
if (val == 1) {
|
||
allocator.deallocate(this);
|
||
}
|
||
}
|
||
static SharedStringData<K>* create(size_t l, Allocatorable auto& allocator) {
|
||
size_t size = sizeof(SharedStringData<K>) + (l + 1) * sizeof(K);
|
||
return new (allocator.allocate(size)) SharedStringData();
|
||
}
|
||
static SharedStringData<K>* from_str(const K* p) {
|
||
return (SharedStringData<K>*)p - 1;
|
||
}
|
||
K* place(K* p, size_t len) {
|
||
ch_traits<K>::copy(p, str(), len);
|
||
return p + len;
|
||
}
|
||
};
|
||
|
||
// Дефолтный аллокатор для строк, может работать статически
|
||
// Default allocator for strings, can work statically
|
||
class string_common_allocator {
|
||
public:
|
||
void* allocate(size_t bytes) {
|
||
return new char[bytes];
|
||
}
|
||
void deallocate(void* address) noexcept {
|
||
delete [] static_cast<char*>(address);
|
||
}
|
||
};
|
||
|
||
string_common_allocator default_string_allocator_selector(...);
|
||
// Если вы хотите задать свой дефолтный аллокатор для строк, перед включение sstring.h
|
||
// объявите функцию
|
||
// ваш_тип_аллокатора default_string_allocator_selector(int);
|
||
// If you want to set your default allocator for strings, before including sstring.h
|
||
// declare a function
|
||
// your_allocator_type default_string_allocator_selector(int);
|
||
using allocator_string = decltype(default_string_allocator_selector(int(0)));
|
||
|
||
template<typename K, Allocatorable Allocator>
|
||
class sstring;
|
||
|
||
/*
|
||
* Так как у класса несколько базовых классов, MSVC не применяет автоматом empty base optimization,
|
||
* и без явного указания - вставит в начало класса пустые байты, сдвинув поле size на 4-8 байта.
|
||
* Укажем ему явно.
|
||
* Since a class has several base classes, MSVC does not automatically apply empty base optimization,
|
||
* and without explicit indication - will insert empty bytes at the beginning of the class, shifting the size field by 4-8 bytes.
|
||
* Let's tell him explicitly.
|
||
*/
|
||
|
||
/*!
|
||
* @ru @brief Класс мутабельной, владеющей строки. Содержит внутренний буфер для строк заданного размера.
|
||
* @tparam K - тип символа.
|
||
* @tparam N - размер внутреннего строкового буфера не менее N.
|
||
* @tparam forShared - аллоцировать внешний буфер в формате, совместимом с sstring.
|
||
* @tparam Allocator - тип аллокатора.
|
||
* @details "Локальная" строка. Хранит в себе указатель на символы и длину строки, а за ней либо сами данные до N
|
||
* символов + нуль, либо если данные длиннее N, то размер выделенного буфера.
|
||
* При этом, если планируется потом результат переместить в sstring, то для динамического буфера
|
||
* выделяется +n байтов, чтобы потом не копировать данные.
|
||
* @en @brief The mutable, owning string class. Contains an internal buffer for text of a given size.
|
||
* @tparam K - symbol type.
|
||
* @tparam N - the size of the internal string buffer is at least N.
|
||
* @tparam forShared - allocate an external buffer in a format compatible with sstring.
|
||
* @tparam Allocator - allocator type.
|
||
* @details "Local" string. Stores a pointer to characters and the length of the string, followed by either the data itself up to N
|
||
* characters + zero, or if the data is longer than N, then the size of the allocated buffer.
|
||
* At the same time, if you plan to later move the result to sstring, then for a dynamic buffer
|
||
* +n bytes are allocated so as not to copy the data later.
|
||
*/
|
||
template<typename K, size_t N, bool forShared = false, Allocatorable Allocator = allocator_string>
|
||
class decl_empty_bases lstring :
|
||
public str_algs<K, simple_str<K>, lstring<K, N, forShared, Allocator>, true>,
|
||
public str_mutable<K, lstring<K, N, forShared, Allocator>>,
|
||
public str_storable<K, lstring<K, N, forShared, Allocator>, Allocator>,
|
||
public from_utf_convertable<K, lstring<K, N, forShared, Allocator>> {
|
||
public:
|
||
using symb_type = K;
|
||
using my_type = lstring<K, N, forShared, Allocator>;
|
||
using allocator_t = Allocator;
|
||
|
||
enum : size_t {
|
||
LocalCapacity = N | (sizeof(void*) / sizeof(K) - 1), //!< Размер внутреннего буфера в символах, N выравнивается до `sizeof(void*) / sizeof(K)`
|
||
};
|
||
|
||
protected:
|
||
enum : size_t {
|
||
extra = forShared ? sizeof(SharedStringData<K>) : 0,
|
||
};
|
||
|
||
using base_algs = str_algs<K, simple_str<K>, my_type, true>;
|
||
using base_storable = str_storable<K, my_type, Allocator>;
|
||
using base_mutable = str_mutable<K, my_type>;
|
||
using base_utf = from_utf_convertable<K, my_type>;
|
||
using traits = ch_traits<K>;
|
||
|
||
friend base_storable;
|
||
friend base_mutable;
|
||
friend base_utf;
|
||
friend class sstring<K, Allocator>;
|
||
|
||
K* data_;
|
||
// Поле не должно инициализироваться, так как может устанавливаться в базовых конструкторах
|
||
// The field should not be initialized, as it can be set in base constructors
|
||
size_t size_;
|
||
|
||
union {
|
||
// Поле не должно инициализироваться, так как может устанавливаться в базовых конструкторах
|
||
// The field should not be initialized, as it can be set in base constructors
|
||
size_t capacity_;
|
||
K local_[LocalCapacity + 1];
|
||
};
|
||
|
||
void create_empty() {
|
||
data_ = local_;
|
||
size_ = 0;
|
||
local_[0] = 0;
|
||
}
|
||
static size_t calc_capacity(size_t s) {
|
||
size_t real_need = (s + 1) * sizeof(K) + extra;
|
||
size_t aligned_alloced = (real_need + alignof(std::max_align_t) - 1) / alignof(std::max_align_t) * alignof(std::max_align_t);
|
||
return (aligned_alloced - extra) / sizeof(K) - 1;
|
||
}
|
||
|
||
K* init(size_t s) {
|
||
size_ = s;
|
||
if (size_ > LocalCapacity) {
|
||
s = calc_capacity(s);
|
||
data_ = alloc_place(s);
|
||
capacity_ = s;
|
||
} else {
|
||
data_ = local_;
|
||
}
|
||
return str();
|
||
}
|
||
// Методы для себя | Methods for yourself
|
||
bool is_alloced() const noexcept {
|
||
return data_ != local_;
|
||
}
|
||
|
||
void dealloc() {
|
||
if (is_alloced()) {
|
||
base_storable::allocator().deallocate(to_real_address(data_));
|
||
data_ = local_;
|
||
}
|
||
}
|
||
|
||
static K* to_real_address(void* ptr) {
|
||
return reinterpret_cast<K*>(reinterpret_cast<u8s*>(ptr) - extra);
|
||
}
|
||
static K* from_real_address(void* ptr) {
|
||
return reinterpret_cast<K*>(reinterpret_cast<u8s*>(ptr) + extra);
|
||
}
|
||
|
||
K* alloc_place(size_t newSize) {
|
||
return from_real_address(base_storable::allocator().allocate((newSize + 1) * sizeof(K) + extra));
|
||
}
|
||
// Вызывается при replace, когда меняют на более длинную замену
|
||
// Called on replace when changing to a longer replacement
|
||
K* alloc_for_copy(size_t newSize) {
|
||
if (capacity() >= newSize) {
|
||
// Замена войдёт в текущий буфер
|
||
// Replacement will go into the current buffer
|
||
return data_;
|
||
}
|
||
return alloc_place(calc_capacity(newSize));
|
||
}
|
||
// Вызывается после replace, когда меняли на более длинную замену, могли скопировать в новый буфер
|
||
// Called after replace, when they changed to a longer replacement, they could have copied it to a new buffer
|
||
void set_from_copy(K* ptr, size_t newSize) {
|
||
if (ptr != data_) {
|
||
// Да, копировали в новый буфер
|
||
// Yes, copied to a new buffer
|
||
dealloc();
|
||
data_ = ptr;
|
||
capacity_ = calc_capacity(newSize);
|
||
}
|
||
size_ = newSize;
|
||
data_[newSize] = 0;
|
||
}
|
||
|
||
public:
|
||
using base_storable::base_storable;
|
||
using base_utf::base_utf;
|
||
|
||
lstring() = default;
|
||
|
||
~lstring() {
|
||
dealloc();
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Копирование из другой строки такого же типа.
|
||
* @param other - другая строка.
|
||
* @en @brief Copy from another string of the same type.
|
||
* @param other - another string.
|
||
*/
|
||
lstring(const my_type& other) : base_storable(other.allocator()) {
|
||
if (other.size_) {
|
||
traits::copy(init(other.size_), other.symbols(), other.size_ + 1);
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Копирование из другой строки такого же типа, но с другим аллокатором.
|
||
* @param other - другая строка.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Copy from another string of the same type, but with a different allocator.
|
||
* @param other - another string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires(sizeof...(Args) > 0 && std::is_convertible_v<allocator_t, Args...>)
|
||
lstring(const my_type& other, Args&&... args) : base_storable(std::forward<Args>(args)...) {
|
||
if (other.size_) {
|
||
traits::copy(init(other.size_), other.symbols(), other.size_ + 1);
|
||
}
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Конструктор из строкового литерала.
|
||
* @param value - строковый литерал.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief String literal constructor.
|
||
* @param value - string literal.
|
||
* @param ...args - parameter for initialization allocator.
|
||
*/
|
||
template<typename T, size_t I = const_lit_for<K, T>::Count, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
constexpr lstring(T&& value, Args&&... args) : base_storable(std::forward<Args>(args)...) {
|
||
if constexpr (I > 1) {
|
||
K* ptr = init(I - 1);
|
||
traits::copy(ptr, value, I - 1);
|
||
ptr[I - 1] = 0;
|
||
} else
|
||
create_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор перемещения из строки такого же типа.
|
||
* @param other - другая строка.
|
||
* @en @brief Constructor for moving from a string of the same type.
|
||
* @param other - another string.
|
||
*/
|
||
lstring(my_type&& other) noexcept : base_storable(std::move(other.allocator())) {
|
||
if (other.size_) {
|
||
size_ = other.size_;
|
||
if (other.is_alloced()) {
|
||
data_ = other.data_;
|
||
capacity_ = other.capacity_;
|
||
} else {
|
||
data_ = local_;
|
||
traits::copy(local_, other.local_, size_ + 1);
|
||
}
|
||
other.data_ = other.local_;
|
||
other.size_ = 0;
|
||
other.local_[0] = 0;
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор заполнения с помощью функтора (см. str_mutable::fill).
|
||
* @param op - функтов заполнения.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief A fill constructor using a functor (see str_mutable::fill).
|
||
* @param op - filling functions.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename Op, typename... Args>
|
||
requires(std::is_constructible_v<Allocator, Args...> && (std::is_invocable_v<Op, my_type&> || std::is_invocable_v<Op, K*, size_t>))
|
||
lstring(const Op& op, Args&&... args) : base_storable(std::forward<Args>(args)...) {
|
||
this->operator<<(op);
|
||
}
|
||
|
||
// copy and swap для присваиваний здесь не очень применимо, так как для строк с большим локальным буфером лишняя копия даже перемещением будет дорого стоить
|
||
// Поэтому реализуем копирующее и перемещающее присваивание отдельно
|
||
// copy and swap for assignments is not very applicable here, since for strings with a large local buffer, an extra copy, even by moving, will be expensive
|
||
// Therefore, we implement the copy and move assignment separately
|
||
|
||
/*!
|
||
* @ru @brief Оператор присваивания копией из строки такого же типа.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Copy assignment operator from a string of the same type.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& operator=(const my_type& other) {
|
||
// Так как между этими объектами не может быть косвенной зависимости, достаточно проверить только на равенство
|
||
// Since there cannot be an indirect dependency between these objects, it is enough to check only for equality
|
||
if (&other != this) {
|
||
traits::copy(reserve_no_preserve(other.size_), other.data_, other.size_ + 1);
|
||
size_ = other.size_;
|
||
}
|
||
return *this;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присваивания перемещением из строки такого же типа.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator by moving from a string of the same type.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& operator=(my_type&& other) noexcept {
|
||
// Так как между этими объектами не может быть косвенной зависимости, достаточно проверить только на равенство
|
||
// Since there cannot be an indirect dependency between these objects, it is enough to check only for equality
|
||
if (&other != this) {
|
||
dealloc();
|
||
if (other.is_alloced()) {
|
||
data_ = other.data_;
|
||
capacity_ = other.capacity_;
|
||
} else {
|
||
traits::copy(data_, other.local_, other.size_ + 1);
|
||
}
|
||
base_storable::allocator() = std::move(other.allocator());
|
||
size_ = other.size_;
|
||
other.create_empty();
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
my_type& assign(const K* other, size_t len) {
|
||
if (len) {
|
||
bool isIntersect = other >= data_ && other + len <= data_ + size_;
|
||
if (isIntersect) {
|
||
// Особый случай, нам пытаются присвоить кусок нашей же строки.
|
||
// Просто переместим текст в буфере, и установим новый размер
|
||
// A special case, they are trying to assign us a piece of our own string.
|
||
// Just move the text in the buffer and set a new size
|
||
if (other > data_) {
|
||
traits::move(data_, other, len);
|
||
}
|
||
} else {
|
||
traits::copy(reserve_no_preserve(len), other, len);
|
||
}
|
||
}
|
||
size_ = len;
|
||
data_[size_] = 0;
|
||
return *this;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присваивания из simple_str.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator from simple_str.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& operator=(simple_str<K> other) {
|
||
return assign(other.str, other.len);
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присваивания строкового литерала.
|
||
* @param other - строковый литерал, копируется в буфер строки.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief String literal assignment operator.
|
||
* @param other - string literal, copied to the string buffer.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t S = const_lit_for<K, T>::Count>
|
||
my_type& operator=(T&& other) {
|
||
return assign(other, S - 1);
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присаивания строкового выражения.
|
||
* @param expr - строковое выражение, материализуемое в буфер строки.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @details Если в строковом выражении что-либо ссылается на части этой же строки, то результат не определён.
|
||
* @en @brief String expression appending operator.
|
||
* @param expr - a string expression materialized into the string buffer.
|
||
* @return my_type& - a reference to yourself.
|
||
* @details If anything in a string expression refers to parts of the same string, then the result is undefined.
|
||
*/
|
||
my_type& operator=(const StrExprForType<K> auto& expr) {
|
||
size_t newLen = expr.length();
|
||
if (newLen) {
|
||
expr.place(reserve_no_preserve(newLen));
|
||
}
|
||
size_ = newLen;
|
||
data_[size_] = 0;
|
||
return *this;
|
||
}
|
||
/// @ru Длина строки. @en String length.
|
||
size_t length() const noexcept {
|
||
return size_;
|
||
}
|
||
/// @ru Указатель на константные символы. @en Pointer to constant characters.
|
||
const K* symbols() const noexcept {
|
||
return data_;
|
||
}
|
||
/// @ru Указатель на буфер строки. @en Pointer to a string buffer.
|
||
K* str() noexcept {
|
||
return data_;
|
||
}
|
||
/// @ru Пустая ли строка. @en Is the string empty?
|
||
bool is_empty() const noexcept {
|
||
return size_ == 0;
|
||
}
|
||
/// @ru Пустая ли строка, для совместимости с std::string. @en Whether the string is empty, for compatibility with std::string.
|
||
bool empty() const noexcept {
|
||
return size_ == 0;
|
||
}
|
||
/// @ru Текущая ёмкость буфера строки. @en Current row buffer capacity.
|
||
size_t capacity() const noexcept {
|
||
return is_alloced() ? capacity_ : LocalCapacity;
|
||
}
|
||
/*!
|
||
* @ru @brief Выделить буфер, достаточный для размещения newSize символов плюс завершающий ноль.
|
||
* @param newSize - новый размер строки.
|
||
* @return K* - указатель на буфер.
|
||
* @details Содержимое буфера не определено, и не гарантируется сохранение старого содержимого.
|
||
* Размер строки устанавливается в newSize.
|
||
* @en @brief Allocate a buffer large enough to hold newSize characters plus a terminating null.
|
||
* @param newSize - new string size.
|
||
* @return K* - pointer to the buffer.
|
||
* @details The contents of the buffer are undefined and the old contents are not guaranteed to be retained.
|
||
* The string size is set to newSize.
|
||
*/
|
||
K* reserve_no_preserve(size_t newSize) {
|
||
if (newSize > capacity()) {
|
||
newSize = calc_capacity(newSize);
|
||
K* newData = alloc_place(newSize);
|
||
dealloc();
|
||
data_ = newData;
|
||
capacity_ = newSize;
|
||
}
|
||
return data_;
|
||
}
|
||
/*!
|
||
* @ru @brief Выделить буфер, достаточный для размещения newSize символов плюс завершающий ноль.
|
||
* @param newSize - новый размер строки.
|
||
* @return K* - указатель на буфер.
|
||
* @details Содержимое строки сохраняется. При увеличении буфера размер выделяется не больше запрошенного.
|
||
* Размер строки устанавливается в newSize.
|
||
* @en @brief Allocate a buffer large enough to hold newSize characters plus a terminating null.
|
||
* @param newSize - new string size.
|
||
* @return K* - pointer to the buffer.
|
||
* @details The contents of the string are preserved. When increasing the buffer, the size allocated is no larger than the requested one.
|
||
* The string size is set to newSize.
|
||
*/
|
||
K* reserve(size_t newSize) {
|
||
if (newSize > capacity()) {
|
||
newSize = calc_capacity(newSize);
|
||
K* newData = alloc_place(newSize);
|
||
traits::copy(newData, data_, size_);
|
||
dealloc();
|
||
data_ = newData;
|
||
capacity_ = newSize;
|
||
}
|
||
return data_;
|
||
}
|
||
/*!
|
||
* @ru @brief Устанавливает размер текущей строки, при необходимости выделяя место.
|
||
* @param newSize - новый размер строки.
|
||
* @return K* - указатель на буфер.
|
||
* @details Содержимое строки сохраняется. При увеличении буфера размер выделяется не менее чем 2 старого размера буфера.
|
||
* Размер строки устанавливается в newSize.
|
||
* @en @brief Sets the size of the current string, allocating space if necessary.
|
||
* @param newSize - new string size.
|
||
* @return K* - pointer to the buffer.
|
||
* @details The contents of the string are preserved. When increasing the buffer size, at least 2 times the old buffer size are allocated.
|
||
* The string size is set to newSize.
|
||
*/
|
||
K* set_size(size_t newSize) {
|
||
size_t cap = capacity();
|
||
if (newSize > cap) {
|
||
size_t needPlace = newSize;
|
||
if (needPlace < (cap + 1) * 2) {
|
||
needPlace = (cap + 1) * 2 - 1;
|
||
}
|
||
reserve(needPlace);
|
||
}
|
||
size_ = newSize;
|
||
data_[newSize] = 0;
|
||
return data_;
|
||
}
|
||
/*!
|
||
* @ru @brief Узнать, локальный или внешний буфер используется для символов.
|
||
* @en @brief Find out whether a local or external buffer is used for characters.
|
||
*/
|
||
bool is_local() const noexcept {
|
||
return !is_alloced();
|
||
}
|
||
/*!
|
||
* @ru @brief Определить длину строки.
|
||
* Ищет символ 0 в буфере строки до его ёмкости, после чего устаналивает длину строки по найденному 0.
|
||
* @en @brief Determine the length of the string.
|
||
* Searches for the character 0 in the string buffer to its capacity, and then sets the length of the line to the found 0.
|
||
*/
|
||
void define_size() {
|
||
size_t cap = capacity();
|
||
for (size_t i = 0; i < cap; i++) {
|
||
if (data_[i] == 0) {
|
||
size_ = i;
|
||
return;
|
||
}
|
||
}
|
||
size_ = cap;
|
||
data_[size_] = 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Уменьшает размер внешнего буфера до минимально возможного для хранения строки.
|
||
* Если строка уместится во внутренний буфер - копирует её в него и освобождает внешний буфер.
|
||
* @en @brief Reduces the size of the external buffer to the smallest possible size to hold the string.
|
||
* If the string fits into the internal buffer, it copies it into it and frees the external buffer.
|
||
*/
|
||
void shrink_to_fit() {
|
||
size_t need_capacity = calc_capacity(size_);
|
||
if (is_alloced() && capacity_ > need_capacity) {
|
||
K* newData = size_ <= LocalCapacity ? local_ : alloc_place(need_capacity);
|
||
traits::copy(newData, data_, size_ + 1);
|
||
base_storable::allocator().deallocate(to_real_address(data_));
|
||
data_ = newData;
|
||
|
||
if (size_ > LocalCapacity) {
|
||
capacity_ = need_capacity;
|
||
}
|
||
}
|
||
}
|
||
/// @ru Делает строку пустой, не меняя буфер строки. @en Makes a string empty without changing the string buffer.
|
||
void clear() {
|
||
set_size(0);
|
||
}
|
||
/// @ru Делает строку пустой и освобождает внешний буфер, если он был. @en Makes the string empty and frees the external buffer, if there was one.
|
||
void reset() {
|
||
dealloc();
|
||
local_[0] = 0;
|
||
size_ = 0;
|
||
}
|
||
};
|
||
|
||
template<size_t N = 15>
|
||
using lstringa = lstring<u8s, N>;
|
||
template<size_t N = 15>
|
||
using lstringw = lstring<wchar_t, N>;
|
||
template<size_t N = 15>
|
||
using lstringu = lstring<u16s, N>;
|
||
template<size_t N = 15>
|
||
using lstringuu = lstring<u32s, N>;
|
||
|
||
template<size_t N = 15>
|
||
using lstringsa = lstring<u8s, N, true>;
|
||
template<size_t N = 15>
|
||
using lstringsw = lstring<wchar_t, N, true>;
|
||
template<size_t N = 15>
|
||
using lstringsu = lstring<u16s, N, true>;
|
||
template<size_t N = 15>
|
||
using lstringsuu = lstring<u32s, N, true>;
|
||
|
||
|
||
template<typename T, typename K = typename const_lit<T>::symb_type>
|
||
auto getLiteralType(T&&) {
|
||
return K{};
|
||
};
|
||
|
||
template<size_t Arch, size_t L>
|
||
inline constexpr const size_t _local_count = 0;
|
||
|
||
template<>
|
||
inline constexpr const size_t _local_count<8, 1> = 23;
|
||
template<>
|
||
inline constexpr const size_t _local_count<8, 2> = 15;
|
||
template<>
|
||
inline constexpr const size_t _local_count<8, 4> = 7;
|
||
template<>
|
||
inline constexpr const size_t _local_count<4, 1> = 15;
|
||
template<>
|
||
inline constexpr const size_t _local_count<4, 2> = 11;
|
||
template<>
|
||
inline constexpr const size_t _local_count<4, 4> = 5;
|
||
|
||
template<typename T>
|
||
constexpr const size_t local_count = _local_count<sizeof(size_t), sizeof(T)>;
|
||
|
||
/*!
|
||
* @ru @brief Класс иммутабельной владеющей строки.
|
||
* @tparam K - тип символов.
|
||
* @tparam Allocator - тип аллокатора.
|
||
* @details "shared" строка.
|
||
* Класс с small string optimization плюс разделяемый иммутабельный буфер строки.
|
||
* Так как буфер строки в этом классе иммутабельный, то:
|
||
* Во-первых, нет нужды хранить размер выделенного буфера, мы его всё-равно не будем изменять.
|
||
* Во-вторых, появляется ещё один тип строки - строка, инициализированная строковым литералом.
|
||
* Для неё просто сохраняем указатель на символы, и не считаем ссылки.
|
||
* Таким образом, инициализация строкового объекта в программе литералом - ничего никуда не копирует -
|
||
* ни в себя, ни в динамическую память, и не стоит дороже по сравнению с инициализацией
|
||
* сырого указателя на строку, и даже ещё оптимальнее, так как ещё и сразу подставляет размер,
|
||
* а не вычисляет его в рантайме.
|
||
* ```cpp
|
||
* stringa text = "text or very very very long text"; // ничего не стоит!
|
||
* stringa copy = anotherString; // Стоит только копирование байтов самого объекта плюс возможно один атомарный инкремент
|
||
* ```
|
||
* В случае разделяемого буфера размер строки всё-равно храним не в общем буфере, а в каждом объекте.
|
||
* Из-за SSO места всё-равно хватает, а в память лезть за длиной придётся меньше.
|
||
* Например, подсчитать сумму длин строк в векторе - пройдётся только по памяти в векторе.
|
||
*
|
||
* Размеры для x64:
|
||
* - для u8s - 24 байта, хранит строки до 23 символов + 0
|
||
* - для u16s - 32 байта, хранит строки до 15 символов + 0
|
||
* - для u32s - 32 байта, хранит строки до 7 символов + 0
|
||
* @en @brief Immutable owning string class.
|
||
* @tparam K - character type.
|
||
* @tparam Allocator - allocator type.
|
||
* @details "shared" string.
|
||
* Class with small string optimization plus a shared immutable string buffer.
|
||
* Since the string buffer in this class is immutable, then:
|
||
* Firstly, there is no need to store the size of the allocated buffer; we will not change it anyway.
|
||
* Secondly, another type of string appears - a string initialized with a string literal.
|
||
* For it, we simply save a pointer to symbols and do not count references.
|
||
* Thus, initializing a string object in a program with a literal does not copy anything anywhere -
|
||
* neither into itself nor into dynamic memory, and does not cost more than initialization
|
||
* a raw pointer to a string, and even more optimal, since it also immediately substitutes the size,
|
||
* but does not calculate it at runtime.
|
||
* ```cpp
|
||
* stringa text = "text or very very very long text"; // costs nothing!
|
||
* string copy = anotherString; // All you need to do is copy the bytes of the object itself, plus possibly one atomic increment
|
||
* ```
|
||
* In the case of a shared buffer, the size of the string is still stored not in the shared buffer, but in each object.
|
||
* Because of SSO, there is still enough space, and you will have to go to memory less for the length.
|
||
* For example, calculating the sum of the lengths of strings in a vector will only go through the memory in the vector.
|
||
*
|
||
* Sizes for x64:
|
||
* - for u8s - 24 bytes, stores strings up to 23 characters + 0
|
||
* - for u16s - 32 bytes, stores strings of up to 15 characters + 0
|
||
* - for u32s - 32 bytes, stores strings of up to 7 characters + 0
|
||
*/
|
||
template<typename K, Allocatorable Allocator = allocator_string>
|
||
class decl_empty_bases sstring :
|
||
public str_algs<K, simple_str<K>, sstring<K, Allocator>, false>,
|
||
public str_storable<K, sstring<K, Allocator>, Allocator>,
|
||
public from_utf_convertable<K, sstring<K, Allocator>> {
|
||
public:
|
||
using symb_type = K;
|
||
using uns_type = std::make_unsigned_t<K>;
|
||
using my_type = sstring<K, Allocator>;
|
||
using allocator_t = Allocator;
|
||
|
||
enum { LocalCount = local_count<K> };
|
||
|
||
protected:
|
||
using base_algs = str_algs<K, simple_str<K>, my_type, false>;
|
||
using base_storable = str_storable<K, my_type, Allocator>;
|
||
using base_utf = from_utf_convertable<K, my_type>;
|
||
using traits = ch_traits<K>;
|
||
using uni = unicode_traits<K>;
|
||
|
||
friend base_storable;
|
||
friend base_utf;
|
||
|
||
enum Types { Local, Constant, Shared };
|
||
|
||
union {
|
||
// Когда у нас короткая строка, она лежит в самом объекте, а в localRemain
|
||
// пишется, сколько символов ещё можно вписать. Когда строка занимает всё
|
||
// возможное место, то localRemain становится 0, type в этом случае тоже 0,
|
||
// и в итоге после символов строки получается 0, как и надо!
|
||
// When we have a short string, it lies in the object itself, and in localRemain
|
||
// writes how many more characters can be entered. When a line takes up everything
|
||
// possible location, then localRemain becomes 0, type in this case is also 0,
|
||
// and as a result, after the characters of the line we get 0, as it should!
|
||
struct {
|
||
K buf_[LocalCount]; // Локальный буфер строки | Local line buffer
|
||
uns_type localRemain_ : sizeof(uns_type) * CHAR_BIT - 2;
|
||
uns_type type_ : 2;
|
||
};
|
||
struct {
|
||
union {
|
||
// Указатель на конcтантную строку | Pointer to a constant string
|
||
const K* cstr_;
|
||
// Указатель на строку, перед которой лежит SharedStringData
|
||
// Pointer to the string preceded by SharedStringData
|
||
const K* sstr_;
|
||
};
|
||
size_t bigLen_; // Длина не локальной строки | Non-local string length
|
||
};
|
||
};
|
||
|
||
void create_empty() {
|
||
type_ = Local;
|
||
localRemain_ = LocalCount;
|
||
buf_[0] = 0;
|
||
}
|
||
K* init(size_t s) {
|
||
if (s > LocalCount) {
|
||
type_ = Shared;
|
||
localRemain_ = 0;
|
||
bigLen_ = s;
|
||
sstr_ = SharedStringData<K>::create(s, base_storable::allocator())->str();
|
||
return (K*)sstr_;
|
||
} else {
|
||
type_ = Local;
|
||
localRemain_ = LocalCount - s;
|
||
return buf_;
|
||
}
|
||
}
|
||
|
||
K* set_size(size_t newSize) {
|
||
// Вызывается при создании строки при необходимости изменить размер.
|
||
// Других ссылок на shared buffer нет.
|
||
// Called when a string is created and needs to be resized.
|
||
// There are no other references to the shared buffer.
|
||
size_t size = length();
|
||
if (newSize != size) {
|
||
if (type_ == Constant) {
|
||
bigLen_ = newSize;
|
||
} else {
|
||
if (newSize <= LocalCount) {
|
||
if (type_ == Shared) {
|
||
SharedStringData<K>* str_buf = SharedStringData<K>::from_str(sstr_);
|
||
traits::copy(buf_, sstr_, newSize);
|
||
str_buf->decr(base_storable::allocator());
|
||
}
|
||
type_ = Local;
|
||
localRemain_ = LocalCount - newSize;
|
||
} else {
|
||
if (type_ == Shared) {
|
||
if (newSize > size || (newSize > 64 && newSize <= size * 3 / 4)) {
|
||
K* newStr = SharedStringData<K>::create(newSize, base_storable::allocator())->str();
|
||
traits::copy(newStr, sstr_, newSize);
|
||
SharedStringData<K>::from_str(sstr_)->decr(base_storable::allocator());
|
||
sstr_ = newStr;
|
||
}
|
||
} else if (type_ == Local) {
|
||
K* newStr = SharedStringData<K>::create(newSize, base_storable::allocator())->str();
|
||
if (size)
|
||
traits::copy(newStr, buf_, size);
|
||
sstr_ = newStr;
|
||
type_ = Shared;
|
||
localRemain_ = 0;
|
||
}
|
||
bigLen_ = newSize;
|
||
}
|
||
}
|
||
}
|
||
K* str = type_ == Local ? buf_ : (K*)sstr_;
|
||
str[newSize] = 0;
|
||
return str;
|
||
}
|
||
|
||
public:
|
||
using base_storable::base_storable;
|
||
using base_utf::base_utf;
|
||
|
||
sstring() = default;
|
||
|
||
/*!
|
||
* @ru @brief Конструктор пустой строки.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @en @brief Constructor for the empty string.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
*/
|
||
template<typename... Args>
|
||
requires(sizeof...(Args) > 0 && std::is_constructible_v<Allocator, Args...>)
|
||
sstring(Args&&... args) : Allocator(std::forward<Args>(args)...) {}
|
||
|
||
static const sstring<K> empty_str;
|
||
/// @ru Деструктор строки. @en String destructor.
|
||
~sstring() {
|
||
if (type_ == Shared) {
|
||
SharedStringData<K>::from_str(sstr_)->decr(base_storable::allocator());
|
||
}
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор копирования строки.
|
||
* @param other - копируемая строка.
|
||
* @en @brief String copy constructor.
|
||
* @param other - the string to be copied.
|
||
*/
|
||
sstring(const my_type& other) noexcept : base_storable(other.allocator()) {
|
||
memcpy(buf_, other.buf_, sizeof(buf_) + sizeof(K));
|
||
if (type_ == Shared)
|
||
SharedStringData<K>::from_str(sstr_)->incr();
|
||
}
|
||
/*!
|
||
* @ru @brief Конструктор перемещения.
|
||
* @param other - перемещаемая строка.
|
||
* @en @brief Move constructor.
|
||
* @param other - the string to be moved.
|
||
*/
|
||
sstring(my_type&& other) noexcept : base_storable(std::move(other.allocator())) {
|
||
memcpy(buf_, other.buf_, sizeof(buf_) + sizeof(K));
|
||
other.create_empty();
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Конструктор перемещения из lstring с совместимым с sstring внешним буфером.
|
||
* @param src - перемещаемая строка.
|
||
* @details В случае, если символы в lstring лежат во внешнем аллоцированном буфере,
|
||
* просто забираем указатель на буфер, он нам подойдёт.
|
||
* @en @brief A move constructor from lstring with an sstring-compatible external buffer.
|
||
* @param src - the string to be moved.
|
||
* @details If the characters in lstring are in an external allocated buffer,
|
||
* we just take the pointer to the buffer, it will suit us.
|
||
*/
|
||
template<size_t N>
|
||
sstring(lstring<K, N, true, Allocator>&& src) : base_storable(std::move(src.allocator())) {
|
||
size_t size = src.length();
|
||
if (size) {
|
||
if (src.is_alloced()) {
|
||
// Там динамический буфер, выделенный с запасом для SharedStringData.
|
||
// There is a dynamic buffer allocated with a reserve for SharedStringData.
|
||
K* str = src.str();
|
||
if (size > LocalCount) {
|
||
// Просто присвоим его себе.
|
||
// Let's just assign it to ourselves.
|
||
sstr_ = str;
|
||
bigLen_ = size;
|
||
type_ = Shared;
|
||
localRemain_ = 0;
|
||
new (SharedStringData<K>::from_str(str)) SharedStringData<K>();
|
||
} else {
|
||
// Скопируем локально
|
||
// Copy locally
|
||
type_ = Local;
|
||
localRemain_ = LocalCount - size;
|
||
traits::copy(buf_, str, size + 1);
|
||
// Освободим тот буфер, у локальной строки буфер не разделяется с другими
|
||
// Let's free that buffer; a local string's buffer is not shared with others
|
||
src.dealloc();
|
||
}
|
||
} else {
|
||
// Копируем из локального буфера
|
||
// Copy from local buffer
|
||
K* str = init(src.size_);
|
||
traits::copy(str, src.symbols(), size + 1);
|
||
}
|
||
src.create_empty();
|
||
} else
|
||
create_empty();
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Инициализация из строкового литерала.
|
||
* @param s - строковый литерал.
|
||
* @param ...args - параметры для инициализации аллокатора.
|
||
* @details В этом случае просто запоминаем указатель на строку и её длину.
|
||
* @en @brief Initialize from a string literal.
|
||
* @param s - string literal.
|
||
* @param ...args - parameters for initializing the allocator.
|
||
* @details In this case, we simply remember the pointer to the string and its length.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count, typename... Args>
|
||
requires std::is_constructible_v<allocator_t, Args...>
|
||
sstring(T&& s, Args&&... args) : base_storable(std::forward<Args>(args)...) {
|
||
type_ = Constant;
|
||
localRemain_ = 0;
|
||
cstr_ = s;
|
||
bigLen_ = N - 1;
|
||
}
|
||
|
||
void swap(my_type&& other) noexcept {
|
||
char buf[sizeof(buf_) + sizeof(K)];
|
||
memcpy(buf, buf_, sizeof(buf));
|
||
memcpy(buf_, other.buf_, sizeof(buf));
|
||
memcpy(other.buf_, buf, sizeof(buf));
|
||
|
||
std::swap(base_storable::allocator(), other.allocator());
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения другой строки того же типа.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator to another string of the same type.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& operator=(my_type other) noexcept {
|
||
swap(std::move(other));
|
||
return *this;
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения другой строки другого типа.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator to another string of a different type.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& operator=(simple_str<K> other) {
|
||
return operator=(my_type{other, base_storable::allocator()});
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения строкового литерала.
|
||
* @param other - строковый литера.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief String literal assignment operator.
|
||
* @param other - string character.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
template<typename T, size_t N = const_lit_for<K, T>::Count>
|
||
my_type& operator=(T&& other) {
|
||
return operator=(my_type{other, base_storable::allocator()});
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения другой строки типа lstring.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator to another string of type lstring.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
template<size_t N, bool forShared, typename A>
|
||
my_type& operator=(const lstring<K, N, forShared, A>& other) {
|
||
return operator=(my_type{other.to_str(), base_storable::allocator()});
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения перемещаемой строки типа lstring с совместимым буфером.
|
||
* @param other - другая строка.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @en @brief Assignment operator to a movable string of type lstring with a compatible buffer.
|
||
* @param other - another string.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
template<size_t N>
|
||
my_type& operator=(lstring<K, N, true, Allocator>&& other) {
|
||
return operator=(my_type{std::move(other)});
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор присвоения строкового выражения.
|
||
* @param expr - строковое выражения.
|
||
* @return my_type& - ссылку на себя же.
|
||
* @details В строковом выражение допустимо ссылаться на части этой же строки, так как сначала создаётся копия.
|
||
* @en @brief String expression assignment operator.
|
||
* @param expr - string expression.
|
||
* @return my_type& - a reference to yourself.
|
||
* @details In a string expression, it is possible to refer to parts of the same string, since a copy is created first.
|
||
*/
|
||
my_type& operator=(const StrExprForType<K> auto& expr) {
|
||
return operator=(my_type{expr, base_storable::allocator()});
|
||
}
|
||
/*!
|
||
* @ru @brief Сделать строку пустой.
|
||
* @return my_type& - ссылку на себя же
|
||
* @en @brief Make the string empty.
|
||
* @return my_type& - a reference to yourself.
|
||
*/
|
||
my_type& make_empty() noexcept {
|
||
if (type_ == Shared)
|
||
SharedStringData<K>::from_str(sstr_)->decr(base_storable::allocator());
|
||
create_empty();
|
||
return *this;
|
||
}
|
||
/// @ru Указатель на символы строки. @en Pointer to characters in the string.
|
||
const K* symbols() const noexcept {
|
||
return type_ == Local ? buf_ : cstr_;
|
||
}
|
||
/// @ru Длина строки. @en Line length.
|
||
size_t length() const noexcept {
|
||
return type_ == Local ? LocalCount - localRemain_ : bigLen_;
|
||
}
|
||
/// @ru Пустая ли строка. @en Is the string empty?
|
||
bool is_empty() const noexcept {
|
||
return length() == 0;
|
||
}
|
||
/// @ru Пустая ли строка, для совместимости с std::string. @en Whether the string is empty, for compatibility with std::string.
|
||
bool empty() const noexcept {
|
||
return is_empty();
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку, отформатированную с помощью `std::sprintf`.
|
||
* @param pattern - форматная строка.
|
||
* @param ...args - аргументы для `sprintf`.
|
||
* @return my_type.
|
||
* @details Для Windows поддерживаются posix позиционные аргументы, используется `_sprintf_p`.
|
||
* @en @brief Get a string formatted with `std::sprintf`.
|
||
* @param pattern - format string.
|
||
* @param ...args - arguments for `sprintf`.
|
||
* @return my_type.
|
||
* @details On Windows, posix positional arguments are supported, using `_sprintf_p`.
|
||
*/
|
||
template<typename... T>
|
||
static my_type printf(const K* pattern, T&&... args) {
|
||
return my_type{lstring<K, 256, true>{}.printf(pattern, std::forward<T>(args)...)};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку, отформатированную с помощью `std::format`.
|
||
* @param fmtString - константная форматная строка.
|
||
* @param ...args - аргументы для `std::format`.
|
||
* @return my_type.
|
||
* @en @brief Get a string formatted with `std::format`.
|
||
* @param fmtString - constant format string.
|
||
* @param ...args - arguments for `std::format`.
|
||
* @return my_type.
|
||
*/
|
||
template<typename... T>
|
||
static my_type format(const FmtString<K, T...>& fmtString, T&&... args) {
|
||
return my_type{lstring<K, 256, true, Allocator>{}.format(fmtString, std::forward<T>(args)...)};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить строку, отформатированную с помощью `std::vformat`.
|
||
* @param fmtString - форматная строка.
|
||
* @param ...args - аргументы для `std::vformat`.
|
||
* @return my_type.
|
||
* @en @brief Get a string formatted with `std::vformat`.
|
||
* @param fmtString - format string.
|
||
* @param ...args - arguments for `std::vformat`.
|
||
* @return my_type.
|
||
*/
|
||
template<typename... T>
|
||
static my_type vformat(simple_str<K> fmtString, T&&... args) {
|
||
return my_type{lstring<K, 256, true, Allocator>{}.vformat(fmtString, std::forward<T>(args)...)};
|
||
}
|
||
};
|
||
|
||
template<typename K, Allocatorable Allocator>
|
||
inline const sstring<K> sstring<K, Allocator>::empty_str{};
|
||
|
||
template<size_t I>
|
||
struct digits_selector {
|
||
using wider_type = uint16_t;
|
||
};
|
||
|
||
template<>
|
||
struct digits_selector<2> {
|
||
using wider_type = uint32_t;
|
||
};
|
||
|
||
template<>
|
||
struct digits_selector<4> {
|
||
using wider_type = uint64_t;
|
||
};
|
||
|
||
template<typename K, typename T>
|
||
constexpr size_t fromInt(K* bufEnd, T val) {
|
||
const char* twoDigit =
|
||
"0001020304050607080910111213141516171819"
|
||
"2021222324252627282930313233343536373839"
|
||
"4041424344454647484950515253545556575859"
|
||
"6061626364656667686970717273747576777879"
|
||
"8081828384858687888990919293949596979899";
|
||
if (val) {
|
||
need_sign<K, std::is_signed_v<T>, T> sign(val);
|
||
K* itr = bufEnd;
|
||
// Когда у нас минимальное отрицательное число, оно не меняется и остается меньше нуля
|
||
// When we have a minimum negative number, it does not change and remains less than zero
|
||
if constexpr (std::is_signed_v<T>) {
|
||
if (val < 0) {
|
||
// Возьмем две последние цифры
|
||
// Take the last two digits
|
||
const char* ptr = twoDigit - (val % 100) * 2;
|
||
*--itr = static_cast<K>(ptr[1]);
|
||
*--itr = static_cast<K>(ptr[0]);
|
||
val /= 100;
|
||
val = -val;
|
||
}
|
||
}
|
||
while (val >= 100) {
|
||
const char* ptr = twoDigit + (val % 100) * 2;
|
||
*--itr = static_cast<K>(ptr[1]);
|
||
*--itr = static_cast<K>(ptr[0]);
|
||
val /= 100;
|
||
}
|
||
if (val < 10) {
|
||
*--itr = static_cast<K>('0' + val);
|
||
} else {
|
||
const char* ptr = twoDigit + val * 2;
|
||
*--itr = static_cast<K>(ptr[1]);
|
||
*--itr = static_cast<K>(ptr[0]);
|
||
}
|
||
sign.after(itr);
|
||
return size_t(bufEnd - itr);
|
||
}
|
||
bufEnd[-1] = '0';
|
||
return 1;
|
||
}
|
||
|
||
template<typename K, typename T>
|
||
struct expr_num {
|
||
using symb_type = K;
|
||
using my_type = expr_num<K, T>;
|
||
|
||
enum { bufSize = 24 };
|
||
mutable T value;
|
||
mutable K buf[bufSize];
|
||
|
||
expr_num(T t) : value(t) {}
|
||
expr_num(expr_num<K, T>&& t) : value(t.value) {}
|
||
|
||
size_t length() const noexcept {
|
||
value = (T)fromInt(buf + bufSize, value);
|
||
return (size_t)value;
|
||
}
|
||
K* place(K* ptr) const noexcept {
|
||
ch_traits<K>::copy(ptr, buf + bufSize - (size_t)value, (size_t)value);
|
||
return ptr + (size_t)value;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Оператор конкатенации для строкового выражения и целого числа.
|
||
* @param a - строковое выражение.
|
||
* @param s - число.
|
||
* @details Число конвертируется в десятичное строковое представление.
|
||
* @en @brief Concatenation operator for string expression and integer.
|
||
* @param a is a string expression.
|
||
* @param s - number.
|
||
* @details The number is converted to a decimal string representation.
|
||
*/
|
||
template<StrExpr A, FromIntNumber T>
|
||
inline constexpr auto operator + (const A& a, T s) {
|
||
return strexprjoin_c<A, expr_num<typename A::symb_type, T>>{a, s};
|
||
}
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Оператор конкатенации для целого числа и строкового выражения.
|
||
* @param s - число.
|
||
* @param a - строковое выражение.
|
||
* @details Число конвертируется в десятичное строковое представление.
|
||
* @en @brief Concatenation operator for integer and string expression.
|
||
* @param s - number.
|
||
* @param a is a string expression.
|
||
* @details The number is converted to a decimal string representation.
|
||
*/
|
||
template<StrExpr A, FromIntNumber T>
|
||
inline constexpr auto operator + (T s, const A& a) {
|
||
return strexprjoin_c<A, expr_num<typename A::symb_type, T>, false>{a, s};
|
||
}
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Преобразование целого числа в строковое выражение.
|
||
* @tparam K - тип символов.
|
||
* @tparam T - тип числа, выводится из аргумента.
|
||
* @param t - число.
|
||
* @details Возвращает строковое выражение, которое генерирует десятичное представление заданного числа.
|
||
* Может использоваться, когда надо конкатенировть число и строковый литерал.
|
||
* @en @brief Convert an integer to a string expression.
|
||
* @tparam K - character type.
|
||
* @tparam T - number type, inferred from the argument.
|
||
* @param t - number.
|
||
* @details Returns a string expression that generates the decimal representation of the given number.
|
||
* Can be used when you need to concatenate a number and a string literal.
|
||
*/
|
||
template<typename K, typename T>
|
||
inline constexpr auto e_num(T t) {
|
||
return expr_num<K, T>{t};
|
||
}
|
||
|
||
template<typename K>
|
||
consteval simple_str_nt<K> select_str(simple_str_nt<u8s> s8, simple_str_nt<uws> sw, simple_str_nt<u16s> s16, simple_str_nt<u32s> s32) {
|
||
if constexpr (std::is_same_v<K, u8s>)
|
||
return s8;
|
||
if constexpr (std::is_same_v<K, uws>)
|
||
return sw;
|
||
if constexpr (std::is_same_v<K, u16s>)
|
||
return s16;
|
||
if constexpr (std::is_same_v<K, u32s>)
|
||
return s32;
|
||
}
|
||
|
||
#define uni_string(K, p) select_str<K>(p, L##p, u##p, U##p)
|
||
|
||
template<typename K> requires (is_one_of_std_char_v<K>)
|
||
struct expr_real {
|
||
using symb_type = K;
|
||
mutable K buf[40];
|
||
mutable size_t l;
|
||
double v;
|
||
expr_real(double d) : v(d) {}
|
||
expr_real(float d) : v(d) {}
|
||
|
||
size_t length() const noexcept {
|
||
printf_selector::snprintf(buf, 40, uni_string(K, "%.16g").str, v);
|
||
l = (size_t)ch_traits<K>::length(buf);
|
||
return l;
|
||
}
|
||
K* place(K* ptr) const noexcept {
|
||
ch_traits<K>::copy(ptr, buf, l);
|
||
return ptr + l;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Оператор конкатенации для строкового выражения и вещественного числа (`float`, `double`).
|
||
* @param a - строковое выражение.
|
||
* @param s - число.
|
||
* @details Число конвертируется в строковое представление через sprintf("%.16g").
|
||
* @en @brief Concatenation operator for string expression and real number (`float`, `double`).
|
||
* @param a is a string expression.
|
||
* @param s - number.
|
||
* @details The number is converted to a string representation via sprintf("%.16g").
|
||
*/
|
||
template<StrExpr A, typename R>
|
||
requires(is_one_of_std_char_v<typename A::symb_type> && (std::is_same_v<R, double> || std::is_same_v<R, float>))
|
||
inline constexpr auto operator+(const A& a, R s) {
|
||
return strexprjoin_c<A, expr_real<typename A::symb_type>>{a, s};
|
||
}
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Оператор конкатенации для вещественного числа (`float`, `double`) и строкового выражения.
|
||
* @param s - число.
|
||
* @param a - строковое выражение.
|
||
* @details Число конвертируется в строковое представление через `sprintf("%.16g")`.
|
||
* @en @brief Concatenation operator for float (`float`, `double`) and string expression.
|
||
* @param s - number.
|
||
* @param a is a string expression.
|
||
* @details The number is converted to a string representation via `sprintf("%.16g")`.
|
||
*/
|
||
template<StrExpr A, typename R>
|
||
requires(is_one_of_std_char_v<typename A::symb_type> && (std::is_same_v<R, double> || std::is_same_v<R, float>))
|
||
inline constexpr auto operator+(R s, const A& a) {
|
||
return strexprjoin_c<A, expr_real<typename A::symb_type>, false>{a, s};
|
||
}
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Преобразование `double` числа в строковое выражение.
|
||
* @param t - число.
|
||
* @details Возвращает строковое выражение, которое генерирует десятичное представление заданного числа.
|
||
* с помощью `sprintf("%.16g")`. Может использоваться, когда надо конкатенировть число и строковый литерал.
|
||
* @en @brief Convert a `double` number to a string expression.
|
||
* @param t - number.
|
||
* @details Returns a string expression that generates the decimal representation of the given number.
|
||
* using `sprintf("%.16g")`. Can be used when you need to concatenate a number and a string literal.
|
||
*/
|
||
template<typename K> requires(is_one_of_std_char_v<K>)
|
||
inline constexpr auto e_real(double t) {
|
||
return expr_real<K>{t};
|
||
}
|
||
|
||
/*
|
||
* Для создания строковых конкатенаций с векторами и списками, сджойненными константным разделителем
|
||
* K - тип символов строки
|
||
* T - тип контейнера строк (vector, list)
|
||
* I - длина разделителя в символах
|
||
* tail - добавлять разделитель после последнего элемента контейнера.
|
||
* Если контейнер пустой, разделитель в любом случае не добавляется
|
||
* skip_empty - пропускать пустые строки без добавления разделителя
|
||
* To create string concatenations with vectors and lists joined by a constant delimiter
|
||
* K is the symbols
|
||
* T - type of string container (vector, list)
|
||
* I - length of separator in characters
|
||
* tail - add a separator after the last element of the container.
|
||
* If the container is empty, the separator is not added anyway
|
||
* skip_empty - skip empty lines without adding a separator
|
||
*/
|
||
template<typename K, typename T, size_t I, bool tail, bool skip_empty>
|
||
struct expr_join {
|
||
using symb_type = K;
|
||
using my_type = expr_join<K, T, I, tail, skip_empty>;
|
||
|
||
const T& s;
|
||
const K* delim;
|
||
|
||
constexpr size_t length() const noexcept {
|
||
size_t l = 0;
|
||
for (const auto& t: s) {
|
||
size_t len = t.length();
|
||
if (len > 0 || !skip_empty) {
|
||
if (I > 0 && l > 0) {
|
||
l += I;
|
||
}
|
||
l += len;
|
||
}
|
||
}
|
||
return l + (tail && I > 0 && (l > 0 || (!skip_empty && s.size() > 0))? I : 0);
|
||
}
|
||
constexpr K* place(K* ptr) const noexcept {
|
||
if (s.empty()) {
|
||
return ptr;
|
||
}
|
||
K* write = ptr;
|
||
for (const auto& t: s) {
|
||
size_t copyLen = t.length();
|
||
if (I > 0 && write != ptr && (copyLen || !skip_empty)) {
|
||
ch_traits<K>::copy(write, delim, I);
|
||
write += I;
|
||
}
|
||
ch_traits<K>::copy(write, t.symbols(), copyLen);
|
||
write += copyLen;
|
||
}
|
||
if (I > 0 && tail && (write != ptr || (!skip_empty && s.size() > 0))) {
|
||
ch_traits<K>::copy(write, delim, I);
|
||
write += I;
|
||
}
|
||
return write;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Получить строковое выражение, конкатенирующее строки в контейнере в одну строку с заданным разделителем.
|
||
* @tparam tail - добавлять ли разделитель после последней строки.
|
||
* @tparam skip_empty - пропускать пустые строки без добавления разделителя.
|
||
* @param s - контейнер со строками, должен поддерживать `range for`.
|
||
* @param d - разделитель, строковый литерал.
|
||
* @en @brief Get a string expression concatenating the strings in the container into a single string with the given delimiter.limiter.limiter.
|
||
* @tparam tail - whether to add a separator after the last line.
|
||
* @tparam skip_empty - skip empty lines without adding a separator.
|
||
* @param s - container with strings, must support `range for`.
|
||
* @param d - delimiter, string literal.
|
||
*/
|
||
template<bool tail = false, bool skip_empty = false, typename L, typename K = typename const_lit<L>::symb_type, size_t I = const_lit<L>::Count, typename T>
|
||
inline constexpr auto e_join(const T& s, L&& d) {
|
||
return expr_join<K, T, I - 1, tail, skip_empty>{s, d};
|
||
}
|
||
|
||
template<typename K, size_t N, size_t L>
|
||
struct expr_replaces {
|
||
using symb_type = K;
|
||
using my_type = expr_replaces<K, N, L>;
|
||
simple_str<K> what;
|
||
const K* pattern;
|
||
const K* repl;
|
||
mutable size_t first_, last_;
|
||
|
||
constexpr expr_replaces(simple_str<K> w, const K* p, const K* r) : what(w), pattern(p), repl(r) {}
|
||
|
||
constexpr size_t length() const {
|
||
size_t l = what.length();
|
||
if constexpr (N == L) {
|
||
return l;
|
||
}
|
||
first_ = what.find(pattern, N, 0);
|
||
if (first_ != str::npos) {
|
||
last_ = first_ + N;
|
||
for (;;) {
|
||
l += L - N;
|
||
size_t next = what.find(pattern, N, last_);
|
||
if (next == str::npos) {
|
||
break;
|
||
}
|
||
last_ = next + N;
|
||
}
|
||
}
|
||
return l;
|
||
}
|
||
constexpr K* place(K* ptr) const noexcept {
|
||
if constexpr (N == L) {
|
||
const K* from = what.symbols();
|
||
for (size_t start = 0; start < what.length();) {
|
||
size_t next = what.find(pattern, N, start);
|
||
if (next == str::npos) {
|
||
next = what.length();
|
||
}
|
||
size_t delta = next - start;
|
||
ch_traits<K>::copy(ptr, from + start, delta);
|
||
ptr += delta;
|
||
ch_traits<K>::copy(ptr, repl, L);
|
||
ptr += L;
|
||
start = next + N;
|
||
}
|
||
return ptr;
|
||
}
|
||
if (first_ == str::npos) {
|
||
return what.place(ptr);
|
||
}
|
||
const K* from = what.symbols();
|
||
for (size_t start = 0, offset = first_; ;) {
|
||
ch_traits<K>::copy(ptr, from + start, offset - start);
|
||
ptr += offset - start;
|
||
ch_traits<K>::copy(ptr, repl, L);
|
||
ptr += L;
|
||
start = offset + N;
|
||
if (start >= last_) {
|
||
size_t tail = what.length() - last_;
|
||
ch_traits<K>::copy(ptr, from + last_, tail);
|
||
ptr += tail;
|
||
break;
|
||
} else {
|
||
offset = what.find(pattern, N, start);
|
||
}
|
||
}
|
||
return ptr;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Получить строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки.
|
||
* @tparam K - тип символа, выводится из первого аргумента.
|
||
* @param w - начальная строка.
|
||
* @param p - строковый литерал, искомая подстрока.
|
||
* @param r - строковый литерал, на что заменять.
|
||
* @en @brief Get a string expression that generates a string with all occurrences of a given substring replaced.
|
||
* @tparam K - the type of the symbol, inferred from the first argument.
|
||
* @param w - starting line.
|
||
* @param p - string literal, searched substring.
|
||
* @param r - string literal, what to replace with.
|
||
*/
|
||
template<typename K, typename T, size_t N = const_lit_for<K, T>::Count, typename X, size_t L = const_lit_for<K, X>::Count>
|
||
requires(N > 1)
|
||
inline constexpr auto e_repl(simple_str<K> w, T&& p, X&& r) {
|
||
return expr_replaces<K, N - 1, L - 1>{w, p, r};
|
||
}
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки.
|
||
* @tparam K - тип строки.
|
||
* @details `e_repl` позволяет заменять только с использование строковых литералов.
|
||
* В случае, когда искомая подстрока или строка замены не известны при компиляции, и задаются в runtime,
|
||
* следует использовать этот тип, например:
|
||
* @en @brief A string expression that generates a string replacing all occurrences of the given substring.
|
||
* @tparam K - string type.
|
||
* @details `e_repl` only allows replacement using string literals.
|
||
* In the case when the required substring or replacement string is not known at compilation, and is set at runtime,
|
||
* this type should be used, for example:
|
||
* @~
|
||
* ```cpp
|
||
* stringa result = "<header>" + expr_replaced<u8s>{source, pattern, repl} + "</header>";
|
||
* ```
|
||
*/
|
||
template<typename K>
|
||
struct expr_replaced {
|
||
using symb_type = K;
|
||
using my_type = expr_replaced<K>;
|
||
simple_str<K> what;
|
||
const simple_str<K> pattern;
|
||
const simple_str<K> repl;
|
||
mutable size_t first_, last_;
|
||
/*!
|
||
* @ru @brief Конструктор.
|
||
* @param w - исходная строка.
|
||
* @param p - искомая подстрока.
|
||
* @param r - строка замены.
|
||
* @en @brief Constructor.
|
||
* @param w - source string.
|
||
* @param p - the searched substring.
|
||
* @param r - replacement string.
|
||
*/
|
||
constexpr expr_replaced(simple_str<K> w, simple_str<K> p, simple_str<K> r) : what(w), pattern(p), repl(r) {}
|
||
|
||
constexpr size_t length() const {
|
||
size_t l = what.length();
|
||
if (pattern.length() == repl.length()) {
|
||
return l;
|
||
}
|
||
first_ = what.find(pattern);
|
||
if (first_ != str::npos) {
|
||
last_ = first_ + pattern.length();
|
||
for (;;) {
|
||
l += repl.length() - pattern.length();
|
||
size_t next = what.find(pattern, last_);
|
||
if (next == str::npos) {
|
||
break;
|
||
}
|
||
last_ = next + pattern.length();
|
||
}
|
||
}
|
||
return l;
|
||
}
|
||
constexpr K* place(K* ptr) const noexcept {
|
||
if (repl.length() == pattern.length()) {
|
||
const K* from = what.symbols();
|
||
for (size_t start = 0; start < what.length();) {
|
||
size_t next = what.find(pattern, start);
|
||
if (next == str::npos) {
|
||
next = what.length();
|
||
}
|
||
size_t delta = next - start;
|
||
ch_traits<K>::copy(ptr, from + start, delta);
|
||
ptr += delta;
|
||
ch_traits<K>::copy(ptr, repl.symbols(), repl.length());
|
||
ptr += repl.length();
|
||
start = next + pattern.length();
|
||
}
|
||
return ptr;
|
||
}
|
||
if (first_ == str::npos) {
|
||
return what.place(ptr);
|
||
}
|
||
const K* from = what.symbols();
|
||
for (size_t start = 0, offset = first_; ;) {
|
||
ch_traits<K>::copy(ptr, from + start, offset - start);
|
||
ptr += offset - start;
|
||
ch_traits<K>::copy(ptr, repl.symbols(), repl.length());
|
||
ptr += repl.length();
|
||
start = offset + pattern.length();
|
||
if (start >= last_) {
|
||
size_t tail = what.length() - last_;
|
||
ch_traits<K>::copy(ptr, from + last_, tail);
|
||
ptr += tail;
|
||
break;
|
||
} else {
|
||
offset = what.find(pattern, start);
|
||
}
|
||
}
|
||
return ptr;
|
||
}
|
||
};
|
||
|
||
template<bool UseVectorForReplace>
|
||
struct replace_search_result_store {
|
||
size_t count_{};
|
||
std::pair<size_t, size_t> replaces_[16];
|
||
};
|
||
|
||
template<>
|
||
struct replace_search_result_store<true> : std::vector<std::pair<size_t, size_t>> {};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Тип для строкового выражения, генерирующее строку, в которой заданные символы заменяются на заданные строки.
|
||
* @tparam K - тип символа.
|
||
* @tparam UseVectorForReplace - использовать вектор для запоминания результатов поиска вхождений символов.
|
||
* @details Этот тип применяется, когда состав символов или соответствующих им замен не известен в compile time,
|
||
* а определяется в runtime. В конструктор передается вектор из пар `символ - строка замены`.
|
||
* Параметр `UseVectorForReplace` задаёт стратегию реализации. Дело в том, что работа любых строковых выражений
|
||
* разбита на две фазы - вызов `length()`, в котором подсчитывется количество символов в результате,
|
||
* и вызов `place()`, в котором результат помещается в предоставленный буфер.
|
||
* При `UseVectorForReplace == true` во время фазы подcчёта количества символов, позиции найденных вхождений
|
||
* сохраняются в векторе, и во время второй фазы поиск уже не выполняется, а позиции берутся из вектора.
|
||
* Это, с одной стороны, уменьшает время во второй фазе - не нужно снова выполнять поиск, но увеличивает
|
||
* время в первой фазе - добавление элементов в вектор не бесплатно, и требует времени.
|
||
* При `UseVectorForReplace == false` во время фазы подcчёта количества символов, в локальном массиве запоминается позиции
|
||
* первых 16 вхождений и их общее количество, а во время второй фазы, если вхождений больше 16, то поиск повторяется,
|
||
* но уже только с позиции 16го вхождения. Это может увеличить время во второй фазе, но сокращает время в первой
|
||
* фазе - не нужно добавлять элементы в вектор, не нужна динамическая аллокация.
|
||
* В разных сценариях использования более оптимальными могут быть та или иная стратегия, и вы можете сами решить,
|
||
* что в каждом конкретном случае больше подойдёт.
|
||
* @en @brief A type for a string expression that generates a string in which the given characters are replaced by the given strings.
|
||
* @tparam K - symbol type.
|
||
* @tparam UseVectorForReplace - use a vector to remember the results of searching for occurrences of characters.
|
||
* @details This type is used when the composition of symbols or their corresponding replacements is not known at compile time,
|
||
* and is defined at runtime. A vector of `character - replacement string` pairs is passed to the constructor.
|
||
* The `UseVectorForReplace` parameter specifies the implementation strategy. The point is that the work of any string expressions
|
||
* is divided into two phases - the `length()` call, which counts the number of characters in the result,
|
||
* and a call to `place()`, which places the result in the provided buffer.
|
||
* When `UseVectorForReplace == true` during the phase of counting the number of characters, the position of the found occurrences
|
||
* are stored in the vector, and during the second phase the search is no longer performed, and the positions are taken from the vector.
|
||
* This, on the one hand, reduces the time in the second phase - there is no need to search again, but it increases
|
||
* time in the first phase - adding elements to the vector is not free, and takes time.
|
||
* When `UseVectorForReplace == false` during the phase of counting the number of characters, positions in the local array are remembered
|
||
* the first 16 occurrences and their total number, and during the second phase, if there are more than 16 occurrences, then the search is repeated,
|
||
* but only from the position of the 16th occurrence. This may increase the time in the second phase, but reduces the time in the first
|
||
* phase - no need to add elements to the vector, no need for dynamic allocation.
|
||
*In different use cases, one or another strategy may be more optimal, and you can decide for yourself
|
||
* whichever is more suitable in each specific case.
|
||
*/
|
||
template<typename K, bool UseVectorForReplace = false>
|
||
struct expr_replace_symbols {
|
||
using symb_type = K;
|
||
inline static const int BIT_SEARCH_TRESHHOLD = 4;
|
||
|
||
const simple_str<K> source_;
|
||
const std::vector<std::pair<K, simple_str<K>>>& replaces_;
|
||
|
||
lstring<K, 32> pattern_;
|
||
|
||
mutable replace_search_result_store<UseVectorForReplace> search_results_;
|
||
|
||
uu8s bit_mask_[sizeof(K) == 1 ? 32 : 64]{};
|
||
/*!
|
||
* @ru @brief Конструктор выражения.
|
||
* @param source - исходная строка.
|
||
* @param repl - вектор из пар "символ->строка замены".
|
||
* @details Пример:
|
||
* @en @brief Expression constructor.
|
||
* @param source - source string.
|
||
* @param repl - a vector of "character->replacement string" pairs.
|
||
* @details Example:
|
||
* @~
|
||
* ```cpp
|
||
stringa result = expr_replace_symbols<u8s, true>{source, {
|
||
{'-', ""},
|
||
{'<', "<"},
|
||
{'>', ">"},
|
||
{'\'', "'"},
|
||
{'\"', """},
|
||
{'&', "&"},
|
||
}};
|
||
* ```
|
||
* @ru Пример приведен для наглядности использования. В данном случае и заменяемые символы, и строки замены
|
||
* известны в compile time, и в этом случае лучше применять e_repl_const_symbols, а этот класс
|
||
* используется, когда символы или замены задаются в runtime.
|
||
* @en An example is provided for clarity of use. In this case, both the characters to be replaced and the replacement strings
|
||
* known at compile time, in which case it is better to use e_repl_const_symbols, and this class
|
||
* is used when characters or replacements are specified at runtime.
|
||
*/
|
||
constexpr expr_replace_symbols(simple_str<K> source, const std::vector<std::pair<K, simple_str<K>>>& repl )
|
||
: source_(source), replaces_(repl)
|
||
{
|
||
size_t pattern_len = replaces_.size();
|
||
K* pattern = pattern_.set_size(pattern_len);
|
||
|
||
for (size_t idx = 0; idx < replaces_.size(); idx++) {
|
||
*pattern++ = replaces_[idx].first;
|
||
}
|
||
|
||
if (pattern_len >= BIT_SEARCH_TRESHHOLD) {
|
||
for (size_t idx = 0; idx < pattern_len; idx++) {
|
||
uu8s s = static_cast<uu8s>(pattern_[idx]);
|
||
if constexpr (sizeof(K) == 1) {
|
||
bit_mask_[s >> 3] |= (1 << (s & 7));
|
||
} else {
|
||
if (std::make_unsigned_t<K>(pattern_[idx]) > 255) {
|
||
bit_mask_[32 + (s >> 3)] |= (1 << (s & 7));
|
||
} else {
|
||
bit_mask_[s >> 3] |= (1 << (s & 7));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
size_t length() const {
|
||
size_t l = source_.length();
|
||
auto [fnd, num] = find_first_of(source_.str, source_.len);
|
||
if (fnd == str::npos) {
|
||
return l;
|
||
}
|
||
l += replaces_[num].second.len - 1;
|
||
if constexpr (UseVectorForReplace) {
|
||
search_results_.reserve((l >> 4) + 8);
|
||
search_results_.emplace_back(fnd, num);
|
||
for (size_t start = fnd + 1;;) {
|
||
auto [fnd, idx] = find_first_of(source_.str, source_.len, start);
|
||
if (fnd == str::npos) {
|
||
break;
|
||
}
|
||
search_results_.emplace_back(fnd, idx);
|
||
start = fnd + 1;
|
||
l += replaces_[idx].second.len - 1;
|
||
}
|
||
} else {
|
||
const size_t max_store = std::size(search_results_.replaces_);
|
||
search_results_.replaces_[0] = {fnd, num};
|
||
search_results_.count_++;
|
||
for (size_t start = fnd + 1;;) {
|
||
auto [found, idx] = find_first_of(source_.str, source_.len, start);
|
||
if (found == str::npos) {
|
||
break;
|
||
}
|
||
if (search_results_.count_ < max_store) {
|
||
search_results_.replaces_[search_results_.count_] = {found, idx};
|
||
}
|
||
l += replaces_[idx].second.len - 1;
|
||
search_results_.count_++;
|
||
start = found + 1;
|
||
}
|
||
}
|
||
return l;
|
||
}
|
||
K* place(K* ptr) const noexcept {
|
||
size_t start = 0;
|
||
const K* text = source_.str;
|
||
if constexpr (UseVectorForReplace) {
|
||
for (const auto& [pos, num] : search_results_) {
|
||
size_t delta = pos - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[num].second.place(ptr);
|
||
start = pos + 1;
|
||
}
|
||
} else {
|
||
const size_t max_store = std::size(search_results_.replaces_);
|
||
size_t founded = search_results_.count_;
|
||
for (size_t idx = 0, stop = std::min(founded, max_store); idx < stop; idx++) {
|
||
const auto [pos, num] = search_results_.replaces_[idx];
|
||
size_t delta = pos - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[num].second.place(ptr);
|
||
start = pos + 1;
|
||
}
|
||
if (founded > max_store) {
|
||
founded -= max_store;
|
||
while (founded--) {
|
||
auto [fnd, idx] = find_first_of(source_.str, source_.len, start);
|
||
size_t delta = fnd - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[idx].second.place(ptr);
|
||
start = fnd + 1;
|
||
}
|
||
}
|
||
}
|
||
size_t tail = source_.len - start;
|
||
ch_traits<K>::copy(ptr, text + start, tail);
|
||
return ptr + tail;
|
||
}
|
||
|
||
protected:
|
||
size_t index_of(K s) const {
|
||
return pattern_.find(s);
|
||
}
|
||
|
||
bool is_in_mask(uu8s s) const {
|
||
return (bit_mask_[s >> 3] & (1 << (s & 7))) != 0;
|
||
}
|
||
bool is_in_mask2(uu8s s) const {
|
||
return (bit_mask_[32 + (s >> 3)] & (1 << (s & 7))) != 0;
|
||
}
|
||
|
||
bool is_in_pattern(K s, size_t& idx) const {
|
||
if constexpr (sizeof(K) == 1) {
|
||
if (is_in_mask(s)) {
|
||
idx = index_of(s);
|
||
return true;
|
||
}
|
||
} else {
|
||
if (std::make_unsigned_t<const K>(s) > 255) {
|
||
if (is_in_mask2(s)) {
|
||
return (idx = index_of(s)) != -1;
|
||
}
|
||
} else {
|
||
if (is_in_mask(s)) {
|
||
idx = index_of(s);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
std::pair<size_t, size_t> find_first_of(const K* text, size_t len, size_t offset = 0) const {
|
||
size_t pl = pattern_.length();
|
||
if (pl >= BIT_SEARCH_TRESHHOLD) {
|
||
size_t idx;
|
||
while (offset < len) {
|
||
if (is_in_pattern(text[offset], idx)) {
|
||
return {offset, idx};
|
||
}
|
||
offset++;
|
||
}
|
||
} else {
|
||
while (offset < len) {
|
||
if (size_t idx = index_of(text[offset]); idx != -1) {
|
||
return {offset, idx};
|
||
}
|
||
offset++;
|
||
}
|
||
}
|
||
return {-1, -1};
|
||
}
|
||
};
|
||
|
||
// Строковое выражение для замены символов
|
||
// String expression to replace characters
|
||
template<typename K, size_t N, bool UseVectorForReplace>
|
||
struct expr_replace_const_symbols {
|
||
using symb_type = K;
|
||
inline static const int BIT_SEARCH_TRESHHOLD = 4;
|
||
const K pattern_[N];
|
||
const simple_str<K> source_;
|
||
const simple_str<K> replaces_[N];
|
||
|
||
mutable replace_search_result_store<UseVectorForReplace> search_results_;
|
||
|
||
[[_no_unique_address]]
|
||
uu8s bit_mask_[N >= BIT_SEARCH_TRESHHOLD ? (sizeof(K) == 1 ? 32 : 64) : 0]{};
|
||
|
||
template<typename ... Repl> requires (sizeof...(Repl) == N * 2)
|
||
constexpr expr_replace_const_symbols(simple_str<K> source, Repl&& ... repl) : expr_replace_const_symbols(0, source, std::forward<Repl>(repl)...) {}
|
||
|
||
size_t length() const {
|
||
size_t l = source_.length();
|
||
auto [fnd, num] = find_first_of(source_.str, source_.len);
|
||
if (fnd == str::npos) {
|
||
return l;
|
||
}
|
||
l += replaces_[num].len - 1;
|
||
if constexpr (UseVectorForReplace) {
|
||
search_results_.reserve((l >> 4) + 8);
|
||
search_results_.emplace_back(fnd, num);
|
||
for (size_t start = fnd + 1;;) {
|
||
auto [fnd, idx] = find_first_of(source_.str, source_.len, start);
|
||
if (fnd == str::npos) {
|
||
break;
|
||
}
|
||
search_results_.emplace_back(fnd, idx);
|
||
start = fnd + 1;
|
||
l += replaces_[idx].len - 1;
|
||
}
|
||
} else {
|
||
const size_t max_store = std::size(search_results_.replaces_);
|
||
search_results_.replaces_[0] = {fnd, num};
|
||
search_results_.count_++;
|
||
for (size_t start = fnd + 1;;) {
|
||
auto [found, idx] = find_first_of(source_.str, source_.len, start);
|
||
if (found == str::npos) {
|
||
break;
|
||
}
|
||
if (search_results_.count_ < max_store) {
|
||
search_results_.replaces_[search_results_.count_] = {found, idx};
|
||
}
|
||
l += replaces_[idx].len - 1;
|
||
search_results_.count_++;
|
||
start = found + 1;
|
||
}
|
||
}
|
||
return l;
|
||
}
|
||
K* place(K* ptr) const noexcept {
|
||
size_t start = 0;
|
||
const K* text = source_.str;
|
||
if constexpr (UseVectorForReplace) {
|
||
for (const auto& [pos, num] : search_results_) {
|
||
size_t delta = pos - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[num].place(ptr);
|
||
start = pos + 1;
|
||
}
|
||
} else {
|
||
const size_t max_store = std::size(search_results_.replaces_);
|
||
size_t founded = search_results_.count_;
|
||
for (size_t idx = 0, stop = std::min(founded, max_store); idx < stop; idx++) {
|
||
const auto [pos, num] = search_results_.replaces_[idx];
|
||
size_t delta = pos - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[num].place(ptr);
|
||
start = pos + 1;
|
||
}
|
||
if (founded > max_store) {
|
||
founded -= max_store;
|
||
while (founded--) {
|
||
auto [fnd, idx] = find_first_of(source_.str, source_.len, start);
|
||
size_t delta = fnd - start;
|
||
ch_traits<K>::copy(ptr, text + start, delta);
|
||
ptr += delta;
|
||
ptr = replaces_[idx].place(ptr);
|
||
start = fnd + 1;
|
||
}
|
||
}
|
||
}
|
||
size_t tail = source_.len - start;
|
||
ch_traits<K>::copy(ptr, text + start, tail);
|
||
return ptr + tail;
|
||
}
|
||
|
||
protected:
|
||
template<typename ... Repl>
|
||
constexpr expr_replace_const_symbols(int, simple_str<K> source, K s, simple_str<K> r, Repl&&... repl) :
|
||
expr_replace_const_symbols(0, source, std::forward<Repl>(repl)..., std::make_pair(s, r)){}
|
||
|
||
template<typename ... Repl> requires (sizeof...(Repl) == N)
|
||
constexpr expr_replace_const_symbols(int, simple_str<K> source, Repl&&... repl) :
|
||
source_(source), pattern_ {repl.first...}, replaces_{repl.second...}
|
||
{
|
||
if constexpr (N >= BIT_SEARCH_TRESHHOLD) {
|
||
for (size_t idx = 0; idx < N; idx++) {
|
||
uu8s s = static_cast<uu8s>(pattern_[idx]);
|
||
if constexpr (sizeof(K) == 1) {
|
||
bit_mask_[s >> 3] |= 1 << (s & 7);
|
||
} else {
|
||
if (std::make_unsigned_t<const K>(pattern_[idx]) > 255) {
|
||
bit_mask_[32 + (s >> 3)] |= 1 << (s & 7);
|
||
} else {
|
||
bit_mask_[s >> 3] |= 1 << (s & 7);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
template<size_t Idx>
|
||
size_t index_of(K s) const {
|
||
if constexpr (Idx < N) {
|
||
return pattern_[Idx] == s ? Idx : index_of<Idx + 1>(s);
|
||
}
|
||
return -1;
|
||
}
|
||
bool is_in_mask(uu8s s) const {
|
||
return (bit_mask_[s >> 3] & (1 <<(s & 7))) != 0;
|
||
}
|
||
bool is_in_mask2(uu8s s) const {
|
||
return (bit_mask_[32 + (s >> 3)] & (1 <<(s & 7))) != 0;
|
||
}
|
||
|
||
bool is_in_pattern(K s, size_t& idx) const {
|
||
if constexpr (N >= BIT_SEARCH_TRESHHOLD) {
|
||
if constexpr (sizeof(K) == 1) {
|
||
if (is_in_mask(s)) {
|
||
idx = index_of<0>(s);
|
||
return true;
|
||
}
|
||
} else {
|
||
if (std::make_unsigned_t<const K>(s) > 255) {
|
||
if (is_in_mask2(s)) {
|
||
return (idx = index_of<0>(s)) != -1;
|
||
}
|
||
} else {
|
||
if (is_in_mask(s)) {
|
||
idx = index_of<0>(s);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
std::pair<size_t, size_t> find_first_of(const K* text, size_t len, size_t offset = 0) const {
|
||
if constexpr (N >= BIT_SEARCH_TRESHHOLD) {
|
||
size_t idx;
|
||
while (offset < len) {
|
||
if (is_in_pattern(text[offset], idx)) {
|
||
return {offset, idx};
|
||
}
|
||
offset++;
|
||
}
|
||
} else {
|
||
while (offset < len) {
|
||
if (size_t idx = index_of<0>(text[offset]); idx != -1) {
|
||
return {offset, idx};
|
||
}
|
||
offset++;
|
||
}
|
||
}
|
||
return {-1, -1};
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ingroup StrExprs
|
||
* @ru @brief Возвращает строковое выражение, генерирующее строку, в которой заданные символы
|
||
* заменены на заданные подстроки.
|
||
* @tparam UseVector - использовать вектор для сохранения результатов поиска символов.
|
||
* Более подробно описано в `expr_replace_symbols`.
|
||
* @param src - исходная строка.
|
||
* @param symbol - константный символ, который надо заменять.
|
||
* @param repl - строковый литерал, на который заменять символ.
|
||
* @param ... symbol, repl - другие символы и строки.
|
||
* @details Применяется для генерации замены символов на строки, в случае если все они известны
|
||
* в compile time. Пример:
|
||
* @en @brief Returns a string expression that generates a string containing the given characters
|
||
* replaced with given substrings.
|
||
* @tparam UseVector - use a vector to save symbol search results.
|
||
* Described in more detail in `expr_replace_symbols`.
|
||
* @param src - source string.
|
||
* @param symbol - constant symbol that needs to be replaced.
|
||
* @param repl - string literal to replace the character with.
|
||
* @param ... symbol, repl - other symbols and strings.
|
||
* @details Used to generate character replacements for strings if all of them are known
|
||
* at compile time. Example:
|
||
* @~
|
||
* ```cpp
|
||
* out += "<div>" + e_repl_const_symbols(text, '\"', """, '<', "<", '\'', "'", '&', "&") + "</div>";
|
||
* ```
|
||
* @ru В принипе, `e_repl_const_symbols` вполне безопасно возвращать из функции, если исходная строка
|
||
* внешняя по отношению к функции.
|
||
* @en In principle, `e_repl_const_symbols` is quite safe to return from a function if the source string
|
||
* external to function.
|
||
* @~
|
||
* ```cpp
|
||
* auto repl_html_symbols(ssa text) {
|
||
* return e_repl_const_symbols(text, '\"', """, '<', "<", '\'', "'", '&', "&");
|
||
* }
|
||
* ....
|
||
* out += "<div>" + repl_html_symbols(content) + "</div>";
|
||
* ```
|
||
*/
|
||
template<bool UseVector = false, typename K, typename ... Repl>
|
||
requires (sizeof...(Repl) % 2 == 0)
|
||
auto e_repl_const_symbols(simple_str<K> src, Repl&& ... other) {
|
||
return expr_replace_const_symbols<K, sizeof...(Repl) / 2, UseVector>(src, std::forward<Repl>(other)...);
|
||
}
|
||
|
||
template<typename K, typename H>
|
||
struct StoreType {
|
||
simple_str<K> str;
|
||
size_t hash;
|
||
char node[sizeof(sstring<K>)];
|
||
|
||
const simple_str_nt<K>& to_nt() const noexcept {
|
||
return static_cast<const simple_str_nt<K>&>(str);
|
||
}
|
||
const sstring<K>& to_str() const noexcept {
|
||
return *reinterpret_cast<const sstring<K>*>(node);
|
||
}
|
||
};
|
||
|
||
template<bool Wide>
|
||
struct fnv_const {
|
||
static inline constexpr size_t basis = static_cast<size_t>(14695981039346656037ULL);
|
||
static inline constexpr size_t prime = static_cast<size_t>(1099511628211ULL);
|
||
};
|
||
|
||
template<>
|
||
struct fnv_const<false> {
|
||
static inline constexpr size_t basis = static_cast<size_t>(2166136261U);
|
||
static inline constexpr size_t prime = static_cast<size_t>(16777619U);
|
||
};
|
||
|
||
using fnv = fnv_const<sizeof(size_t) == 8>;
|
||
|
||
template<typename K>
|
||
inline constexpr size_t fnv_hash(const K* ptr, size_t l) {
|
||
size_t h = fnv::basis;
|
||
for (size_t i = 0; i < l; i++) {
|
||
h = (h ^ (std::make_unsigned_t<K>)ptr[i]) * fnv::prime;
|
||
}
|
||
return h;
|
||
};
|
||
|
||
template<typename K>
|
||
inline constexpr size_t fnv_hash_ia(const K* ptr, size_t l) {
|
||
size_t h = fnv::basis;
|
||
for (size_t i = 0; i < l; i++) {
|
||
std::make_unsigned_t<K> s = (std::make_unsigned_t<K>)ptr[i];
|
||
h = (h ^ (s >= 'A' && s <= 'Z' ? s | 0x20 : s)) * fnv::prime;
|
||
}
|
||
return h;
|
||
};
|
||
|
||
template<typename T, typename K = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count>
|
||
inline constexpr size_t fnv_hash(T&& value) {
|
||
size_t h = fnv::basis;
|
||
for (size_t i = 0; i < N - 1; i++) {
|
||
h = (h ^ (std::make_unsigned_t<K>)value[i]) * fnv::prime;
|
||
}
|
||
return h;
|
||
};
|
||
|
||
template<typename T, typename K = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count>
|
||
inline constexpr size_t fnv_hash_ia(T&& value) {
|
||
size_t h = fnv::basis;
|
||
for (size_t i = 0; i < N - 1; i++) {
|
||
std::make_unsigned_t<K> s = (std::make_unsigned_t<K>)value[i];
|
||
h = (h ^ (s >= 'A' && s <= 'Z' ? s | 0x20 : s)) * fnv::prime;
|
||
}
|
||
return h;
|
||
};
|
||
|
||
template<typename K>
|
||
inline consteval size_t fnv_hash_compile(const K* ptr, size_t l) {
|
||
return fnv_hash(ptr, l);
|
||
};
|
||
|
||
template<typename K>
|
||
inline consteval size_t fnv_hash_ia_compile(const K* ptr, size_t l) {
|
||
return fnv_hash_ia(ptr, l);
|
||
};
|
||
|
||
static_assert(std::is_trivially_copyable_v<StoreType<u8s, int>>, "Store type must be trivially copyable");
|
||
|
||
template<typename K>
|
||
struct streql;
|
||
template<typename K>
|
||
struct strhash;
|
||
|
||
/*!
|
||
* @ru @brief Контейнер для более эффективного поиска по строковым ключам.
|
||
* @details Используется для хранения и поиска ключей любого строкового типа.
|
||
* Как unordered_map, но чуть лучше.
|
||
* В качестве ключей хранит simple_str вместе с посчитанным хешем и пустым местом для sstring.
|
||
* После вставки создает в этом пустом месте sstring, чтобы simple_str было на что ссылаться.
|
||
* Позволяет использовать для вставки и поиска любые simstr строковые объекты, создавая из них объект
|
||
* sstring только при реальной вставке.
|
||
* Начиная с С++20 в unordered_map появилась возможность для гетерогенного поиска по ключу с типом,
|
||
* отличным от типа хранящегося ключа, но это требует определённых танцев с типами хэша и сравнения.
|
||
* Поиск же со вставкой (try_emplace) по типу, отличному от типа ключа, появляется в стандарте только с C++26.
|
||
* Имеется реализация хеширования и сравнения для вариантов:
|
||
* - с учётом регистра символов
|
||
* - без учёта регистра символов, только ASCII
|
||
* - без учёта регистра символов, упрощённый Unicode (до 0xFFFF).
|
||
* Хэширование выполняется алгоритмом FNV-1A.
|
||
* Автоматическая конвертация simstr строковых объектов в тип хранящегося ключа (т.е. с посчитанным хешем),
|
||
* а также сохранение sstring в ключе при вставке - выполняется для методов try_emplace, emplace, at,
|
||
* find, operator[], erase. При использовании других методов поиска вам нужно самим обеспечить правильно
|
||
* посчитанный хэш в ключе StoreType. Методами вставки помимо перечисленных не пользоваться, они не
|
||
* сохранят sstring в ключе.
|
||
*
|
||
* Основное, на чём хотелось бы заострить внимание - само назначение strHashMap, для чего оно изначально придумывалось.
|
||
* Цель была не в том, чтобы как-то побить по производительности/памяти unordered_map (иначе зачем делать strHashMap на
|
||
* базе unordered_map?) Основная проблема, которая решалась - это то, что у нас несколько вариантов строковых объектов,
|
||
* и хотелось бы иметь возможность использовать для вставки и поиска ключей любой из типов строковых объектов.
|
||
* У нас есть simple_str, simple_str_nt, sstring, а lstring<N> вообще на каждое N это новый тип объекта.
|
||
* В std всё было просто - там только string, и соответственно, пользовались unordered_map<string, Что-то там>.
|
||
* И между прочим, в std столкнулись с той же проблемой: ключи const char*, а с C++17 и string_view (это как наш simple_str),
|
||
* искать в unordered_map<string, Type> - не очень хорошо, приходится каждый раз ключ преобразовывать в string, хотя можно
|
||
* обойтись и без этого.
|
||
* Поэтому начиная с С++20 в unordered_map таки добавили возможность гетерогенного поиска - на
|
||
* https://en.cppreference.com/w/cpp/container/unordered_map/find это синтаксисы (3) и (4).
|
||
* Правда, для этого требуются ритуальные танцы с бубном - требуется особый тип для хеширования (который, понятно, в
|
||
* стандарт уже не входит и его надо реализовывать самому), который умеет хешировать разные типы ключей,
|
||
* и содержит typename `is_transparent`. Собственно, на той же странице с ним пример и приведён.
|
||
* Часть проблемы это закрыло - поиска, но проблему вставки не решало - для вставки в unordered_map<string, Type>
|
||
* по прежнему требуется только string. И только в будущем С++26 мы ждём возможности гетерогенной вставки в виде
|
||
* try_emplace (https://en.cppreference.com/w/cpp/container/unordered_map/try_emplace) в синтаксисе (6), который
|
||
* позволит принимать ключи других типов и преобразовывать их в нужный тип только если вставка реально осуществляется.
|
||
* В своей строковой библиотеке я столкнулся с этой проблемой ещё до С++17, и так я как не настолько крут, чтобы
|
||
* влиять на стандарт, просто создал свой класс - наследник от unordered_map, с обёртками вокруг методов вставки/поиска.
|
||
* Ну и дополнительно добавил ещё два варианта хеширования/сравнения: без учёта регистра ASCII, без учёта регистра simple unicode.
|
||
* Таким образом, получился класс, который расширяет стандартный unordered_map возможностями по работе со строковыми ключами разных
|
||
* типов, позволяющий избежать лишних ненужных преобразований ключей при поиске и вставке.
|
||
* То, что при этом может улучшится производительность - не цель, а побочный эффект - приятный, если он есть,
|
||
* но и не смертельный, если его нет.
|
||
* @en @brief Container for more efficient searching by string keys.
|
||
* @details Used to store and lookup keys of any string type.
|
||
* Like unordered_map, but a little better.
|
||
* Stores simple_str as keys along with the calculated hash and an empty space for sstring.
|
||
* After insertion, creates an sstring in this empty space so that simple_str has something to refer to.
|
||
* Allows you to use any simstr string objects for insertion and search, creating an object from them
|
||
* sstring only for real insertion.
|
||
* Since C++20, unordered_map has the ability to perform heterogeneous search by key with type,
|
||
* different from the type of the key being stored, but this requires some dancing with hash types and comparisons.
|
||
* Search with insertion (try_emplace) by a type other than the key type appears in the standard only with C++26.
|
||
* There is a hashing and comparison implementation for the options:
|
||
* - case sensitive
|
||
* - case insensitive, ASCII only
|
||
* - case insensitive, simplified Unicode (up to 0xFFFF).
|
||
* Hashing is performed by the FNV-1A algorithm.
|
||
* Automatic conversion of simstr string objects to the type of the stored key (i.e. with a calculated hash),
|
||
* and also saving sstring in the key when inserting - performed for the try_emplace, emplace, at, methods
|
||
* find, operator[], erase. When using other search methods, you need to provide the correct
|
||
* calculated hash in the StoreType key. Do not use insertion methods other than those listed; they are not
|
||
* will save sstring in the key.
|
||
*
|
||
* The main thing I would like to focus on is the very purpose of strHashMap, for which it was originally invented.
|
||
* The goal was not to somehow beat unordered_map in terms of performance/memory (otherwise why make strHashMap on
|
||
* unordered_map database?) The main problem that was solved is that we have several options for string objects,
|
||
* and I would like to be able to use any type of string object to insert and search for keys.
|
||
* We have simple_str, simple_str_nt, sstring, and lstring<N> in general for every N is a new object type.
|
||
* In std everything was simple - there was only string, and accordingly, we used unordered_map<string, Something there>.
|
||
* And by the way, in std we encountered the same problem: the keys are const char*, and with C++17 and string_view (this is like our simple_str),
|
||
* searching in unordered_map<string, Type> is not very good, you have to convert the key to string every time, although you can
|
||
* do without this.
|
||
* Therefore, starting from C++20, the possibility of heterogeneous search was added to unordered_map - on
|
||
* https://en.cppreference.com/w/cpp/container/unordered_map/find these are syntaxes (3) and (4).
|
||
* True, this requires ritual dances with a tambourine - a special type is required for hashing (which, of course, in
|
||
* the standard is no longer included and you need to implement it yourself), which can hash different types of keys,
|
||
* and contains typename `is_transparent`. In fact, there is an example of it on the same page.
|
||
* This solved part of the problem - search, but did not solve the insertion problem - for inserting into unordered_map<string, Type>
|
||
* still only requires string. And only in the future C++26 we expect the possibility of heterogeneous insertion in the form
|
||
* try_emplace (https://en.cppreference.com/w/cpp/container/unordered_map/try_emplace) in syntax (6), which
|
||
* will allow you to accept keys of other types and convert them to the desired type only if the insertion is actually carried out.
|
||
* In my string library, I encountered this problem even before C++17, and so I’m not so cool as to
|
||
* influence the standard, I simply created my own class - a descendant of unordered_map, with wrappers around the insertion/search methods.
|
||
* Well, I also added two more hashing/comparison options: case-insensitive ASCII, case-insensitive simple unicode.
|
||
* Thus, we have a class that extends the standard unordered_map with the ability to work with string keys of different
|
||
* types, allowing you to avoid unnecessary unnecessary key conversions when searching and inserting.
|
||
* The fact that performance may improve is not the goal, but a side effect - pleasant, if there is one,
|
||
* but not fatal if it is not there.
|
||
*/
|
||
template<typename K, typename T, typename H = strhash<K>, typename E = streql<K>>
|
||
class hashStrMap : public std::unordered_map<StoreType<K, H>, T, H, E> {
|
||
protected:
|
||
using InStore = StoreType<K, H>;
|
||
|
||
public:
|
||
using my_type = hashStrMap<K, T, H, E>;
|
||
using hash_t = std::unordered_map<InStore, T, H, E>;
|
||
using hasher = H;
|
||
|
||
hashStrMap() = default;
|
||
hashStrMap(const my_type& other) : hash_t(other) {
|
||
for (const auto& [k, v] : *this) {
|
||
InStore& stored = const_cast<InStore&>(k);
|
||
sstring<K> tmp = *(sstring<K>*)stored.node;
|
||
new (stored.node) sstring<K>(std::move(tmp));
|
||
stored.str.str = stored.to_str().symbols();
|
||
}
|
||
}
|
||
~hashStrMap() {
|
||
for (auto& k: *this)
|
||
((sstring<K>*)k.first.node)->~sstring();
|
||
}
|
||
|
||
hashStrMap(my_type&& o) = default;
|
||
|
||
my_type& operator=(const my_type& other) {
|
||
hash_t::operator=(other);
|
||
for (const auto& [k, v] : *this) {
|
||
InStore& stored = const_cast<InStore&>(k);
|
||
sstring<K> tmp = *(sstring<K>*)stored.node;
|
||
new (stored.node) sstring<K>(std::move(tmp));
|
||
stored.str.str = stored.to_str().symbols();
|
||
}
|
||
return *this;
|
||
};
|
||
my_type& operator=(my_type&&) = default;
|
||
|
||
hashStrMap(std::initializer_list<std::pair<const InStore, T>>&& init) {
|
||
for (const auto& e: init)
|
||
emplace(e.first, e.second);
|
||
}
|
||
|
||
using init_str = std::initializer_list<std::pair<const sstring<K>, T>>;
|
||
|
||
hashStrMap(init_str&& init) {
|
||
for (const auto& e: init)
|
||
emplace(e.first, e.second);
|
||
}
|
||
|
||
// При входе хэш должен быть уже посчитан
|
||
// When entering, the hash must already be calculated
|
||
template<typename... ValArgs>
|
||
auto try_emplace(const InStore& key, ValArgs&&... args) {
|
||
auto it = hash_t::try_emplace(key, std::forward<ValArgs>(args)...);
|
||
if (it.second) {
|
||
InStore& stored = const_cast<InStore&>(it.first->first);
|
||
new (stored.node) sstring<K>(key.str);
|
||
stored.str.str = stored.to_str().symbols();
|
||
}
|
||
return it;
|
||
}
|
||
|
||
static InStore toStoreType(simple_str<K> key) {
|
||
return {key, H{}(key)};
|
||
}
|
||
|
||
template<typename Key, typename... ValArgs>
|
||
requires(std::is_convertible_v<Key, simple_str<K>>)
|
||
auto try_emplace(Key&& key, ValArgs&&... args) {
|
||
auto it = hash_t::try_emplace(toStoreType(key), std::forward<ValArgs>(args)...);
|
||
if (it.second) {
|
||
InStore& stored = const_cast<InStore&>(it.first->first);
|
||
new (stored.node) sstring<K>(std::forward<Key>(key));
|
||
stored.str.str = stored.to_str().symbols();
|
||
}
|
||
return it;
|
||
}
|
||
|
||
template<typename... ValArgs>
|
||
auto emplace(const InStore& key, ValArgs&&... args) {
|
||
auto it = try_emplace(key, std::forward<ValArgs>(args)...);
|
||
if (!it.second) {
|
||
it.first->second = T(std::forward<ValArgs>(args)...);
|
||
}
|
||
return it;
|
||
}
|
||
|
||
template<typename Key, typename... ValArgs>
|
||
requires(std::is_convertible_v<Key, simple_str<K>>)
|
||
auto emplace(Key&& key, ValArgs&&... args) {
|
||
auto it = try_emplace(std::forward<Key>(key), std::forward<ValArgs>(args)...);
|
||
if (!it.second) {
|
||
it.first->second = T(std::forward<ValArgs>(args)...);
|
||
}
|
||
return it;
|
||
}
|
||
|
||
auto& operator[](const InStore& key) {
|
||
return try_emplace(key).first->second;
|
||
}
|
||
|
||
template<typename Key>
|
||
requires(std::is_convertible_v<Key, simple_str<K>>)
|
||
auto& operator[](Key&& key) {
|
||
return try_emplace(std::forward<Key>(key)).first->second;
|
||
}
|
||
|
||
decltype(auto) at(const InStore& key) {
|
||
return hash_t::at(key);
|
||
}
|
||
decltype(auto) at(const InStore& key) const {
|
||
return hash_t::at(key);
|
||
}
|
||
|
||
decltype(auto) at(simple_str<K> key) {
|
||
return hash_t::at(toStoreType(key));
|
||
}
|
||
decltype(auto) at(simple_str<K> key) const {
|
||
return hash_t::at(toStoreType(key));
|
||
}
|
||
|
||
auto find(const InStore& key) const {
|
||
return hash_t::find(key);
|
||
}
|
||
|
||
auto find(simple_str<K> key) const {
|
||
return find(toStoreType(key));
|
||
}
|
||
|
||
auto find(const InStore& key) {
|
||
return hash_t::find(key);
|
||
}
|
||
|
||
auto find(simple_str<K> key) {
|
||
return find(toStoreType(key));
|
||
}
|
||
|
||
auto erase(typename hash_t::const_iterator it) {
|
||
if (it != hash_t::end()) {
|
||
((sstring<K>*)it->first.node)->~sstring();
|
||
}
|
||
return hash_t::erase(it);
|
||
}
|
||
|
||
auto erase(const InStore& key) {
|
||
auto it = hash_t::find(key);
|
||
if (it != hash_t::end()) {
|
||
((sstring<K>*)it->first.node)->~sstring();
|
||
hash_t::erase(it);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
auto erase(simple_str<K> key) {
|
||
return erase(toStoreType(key));
|
||
}
|
||
|
||
bool lookup(const K* txt, T& val) const {
|
||
auto it = find(e_s(txt));
|
||
if (it != hash_t::end()) {
|
||
val = it->second;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool lookup(simple_str<K> txt, T& val) const {
|
||
auto it = find(txt);
|
||
if (it != hash_t::end()) {
|
||
val = it->second;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void clear() {
|
||
for (auto& k: *this)
|
||
((sstring<K>*)k.first.node)->~sstring();
|
||
hash_t::clear();
|
||
}
|
||
bool contains(const InStore& key) const {
|
||
return hash_t::find(key) != this->end();
|
||
}
|
||
|
||
bool contains(simple_str<K> key) const {
|
||
return find(toStoreType(key)) != this->end();
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct streql {
|
||
template<typename H>
|
||
bool operator()(const StoreType<K, H>& _Left, const StoreType<K, H>& _Right) const {
|
||
return _Left.hash == _Right.hash && _Left.str == _Right.str;
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct strhash { // hash functor for basic_string
|
||
size_t operator()(simple_str<K> _Keyval) const {
|
||
return fnv_hash(_Keyval.symbols(), _Keyval.length());
|
||
}
|
||
size_t operator()(const StoreType<K, strhash<K>>& _Keyval) const {
|
||
return _Keyval.hash;
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct streqlia {
|
||
template<typename H>
|
||
bool operator()(const StoreType<K, H>& _Left, const StoreType<K, H>& _Right) const {
|
||
return _Left.hash == _Right.hash && _Left.str.equal_ia(_Right.str);
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct strhashia {
|
||
size_t operator()(simple_str<K> _Keyval) const {
|
||
return fnv_hash_ia(_Keyval.symbols(), _Keyval.length());
|
||
}
|
||
size_t operator()(const StoreType<K, strhashia<K>>& _Keyval) const {
|
||
return _Keyval.hash;
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct streqliu {
|
||
template<typename H>
|
||
bool operator()(const StoreType<K, H>& _Left, const StoreType<K, H>& _Right) const {
|
||
return _Left.hash == _Right.hash && _Left.str.equal_iu(_Right.str);
|
||
}
|
||
};
|
||
|
||
template<typename K>
|
||
struct strhashiu {
|
||
size_t operator()(simple_str<K> _Keyval) const {
|
||
return unicode_traits<K>::hashiu(_Keyval.symbols(), _Keyval.length());
|
||
}
|
||
size_t operator()(const StoreType<K, strhashiu<K>>& _Keyval) const {
|
||
return _Keyval.hash;
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Для построения длинных динамических строк конкатенацией мелких кусочков.
|
||
* @details Выделяет по мере надобности отдельные блоки заданного размера (или кратного ему для больших вставок),
|
||
* чтобы избежать релокации длинных строк. После построения можно слить в одну строку.
|
||
* Как показали замеры, если сливать потом в одну строку, работает медленнее, чем lstring +=,
|
||
* но экономнее по памяти. Если не сливать в одну строку, а дальше перебирать буфера - быстрее.
|
||
* Сам является строковым выражением.
|
||
* @en @brief For constructing long dynamic strings by concatenating small pieces.
|
||
* @details Selects individual blocks of a given size (or a multiple of it for large inserts) as needed.
|
||
* to avoid relocation of long strings. After construction, you can merge it into one line.
|
||
* As measurements have shown, if you then merge it into one line, it works slower than lstring +=,
|
||
* but more economical in memory. If you don't merge it into one line, and then iterate through buffers, it's faster.
|
||
* Itself is a string expression.
|
||
*/
|
||
template<typename K>
|
||
class chunked_string_builder {
|
||
using chunk_t = std::pair<std::unique_ptr<K[]>, size_t>;
|
||
std::vector<chunk_t> chunks; // блоки и длина данных в них | blocks and data length in them
|
||
K* write{}; // Текущая позиция записи | Current write position
|
||
size_t len{}; // Общая длина | Total length
|
||
size_t remain{}; // Сколько осталось места в текущем блоке | How much space is left in the current block
|
||
size_t align{1024};
|
||
|
||
public:
|
||
using my_type = chunked_string_builder<K>;
|
||
using symb_type = K;
|
||
chunked_string_builder() = default;
|
||
chunked_string_builder(size_t a) : align(a){};
|
||
chunked_string_builder(const my_type&) = delete;
|
||
chunked_string_builder(my_type&& other) noexcept
|
||
: chunks(std::move(other.chunks)), write(other.write), len(other.len), remain(other.remain), align(other.align) {
|
||
other.len = other.remain = 0;
|
||
other.write = nullptr;
|
||
}
|
||
my_type& operator=(my_type other) noexcept {
|
||
chunks.swap(other.chunks);
|
||
write = other.write;
|
||
len = other.len;
|
||
remain = other.remain;
|
||
align = other.align;
|
||
other.len = other.remain = 0;
|
||
other.write = nullptr;
|
||
return *this;
|
||
}
|
||
|
||
/// @ru Добавление порции данных. @en Adding a piece of data.
|
||
my_type& operator<<(simple_str<K> data) {
|
||
if (data.len) {
|
||
len += data.len;
|
||
if (data.len <= remain) {
|
||
// Добавляемые данные влезают в выделенный блок, просто скопируем их
|
||
// The added data fits into the selected block, just copy it
|
||
ch_traits<K>::copy(write, data.str, data.len);
|
||
write += data.len; // Сдвинем позицию записи | Let's move the recording position
|
||
chunks.back().second += data.len; // Увеличим длину хранимых в блоке данных | Let's increase the length of the data stored in the block
|
||
remain -= data.len; // Уменьшим остаток места в блоке | Reduce the remaining space in the block
|
||
} else {
|
||
// Не влезают | They don't fit
|
||
if (remain) {
|
||
// Сначала запишем сколько влезет
|
||
// First, write down as much as we can
|
||
ch_traits<K>::copy(write, data.str, remain);
|
||
data.len -= remain;
|
||
data.str += remain;
|
||
chunks.back().second += remain; // Увеличим длину хранимых в блоке данных | Let's increase the length of the data stored in the block
|
||
}
|
||
// Выделим новый блок и впишем в него данные
|
||
// Рассчитаем размер блока, кратного заданному выравниванию
|
||
// Select a new block and write data into it
|
||
// Calculate the block size that is a multiple of the given alignment
|
||
size_t blockSize = (data.len + align - 1) / align * align;
|
||
chunks.emplace_back(std::make_unique<K[]>(blockSize), data.len);
|
||
write = chunks.back().first.get();
|
||
ch_traits<K>::copy(write, data.str, data.len);
|
||
write += data.len;
|
||
remain = blockSize - data.len;
|
||
}
|
||
}
|
||
return *this;
|
||
}
|
||
/// @ru Добавление строкового выражения. @en Adding a string expression.
|
||
my_type& operator<<(const StrExprForType<K> auto& expr) {
|
||
size_t l = expr.length();
|
||
if (l) {
|
||
if (l < remain) {
|
||
write = expr.place(write);
|
||
chunks.back().second += l;
|
||
len += l;
|
||
remain -= l;
|
||
} else if (!remain) {
|
||
size_t blockSize = (l + align - 1) / align * align; // Рассчитаем размер блока, кратного заданному выравниванию
|
||
chunks.emplace_back(std::make_unique<K[]>(blockSize), l);
|
||
write = expr.place(chunks.back().first.get());
|
||
len += l;
|
||
remain = blockSize - l;
|
||
} else {
|
||
auto store = std::make_unique<K[]>(l);
|
||
expr.place(store.get());
|
||
return operator<<({store.get(), l});
|
||
}
|
||
}
|
||
return *this;
|
||
}
|
||
/// @ru Добавление символа. @en Adding a symbol.
|
||
template<typename T>
|
||
my_type& operator<<(T data)
|
||
requires std::is_same_v<T, K>
|
||
{
|
||
return operator<<(expr_char<K>(data));
|
||
}
|
||
/// @ru Длина сохранённого текста. @en Length of the saved text.
|
||
constexpr size_t length() const noexcept {
|
||
return len;
|
||
}
|
||
/// @ru Сбрасывает содержимое, но при этом не удаляет первый буфер, чтобы потом избежать аллокации. @en Resets the contents, but does not delete the first buffer in order to avoid allocation later.
|
||
void reset() {
|
||
if (chunks.empty()) {
|
||
return;
|
||
}
|
||
if (chunks.size() > 1) {
|
||
remain = 0;
|
||
chunks.resize(1);
|
||
}
|
||
remain += chunks[0].second;
|
||
chunks[0].second = 0;
|
||
len = 0;
|
||
write = chunks[0].first.get();
|
||
}
|
||
|
||
constexpr K* place(K* p) const noexcept {
|
||
for (const auto& block: chunks) {
|
||
ch_traits<K>::copy(p, block.first.get(), block.second);
|
||
p += block.second;
|
||
}
|
||
return p;
|
||
}
|
||
/*!
|
||
* @ru @brief Применяет функтор к каждому сохранённому буферу.
|
||
* @tparam Op - тип функтора, функция вида (const K* ptr, size_t len).
|
||
* @param o - функтор.
|
||
* @en @brief Applies a functor to each stored buffer.
|
||
* @tparam Op - type of the functor, function type (const K* ptr, size_t len).
|
||
* @param o is a functor.
|
||
*/
|
||
template<typename Op>
|
||
void out(const Op& o) const {
|
||
for (const auto& block: chunks)
|
||
o(block.first.get(), block.second);
|
||
}
|
||
/*!
|
||
* @ru @brief Проверяет, расположен ли весь текст одним непрерывным куском в памяти.
|
||
* @en @brief Checks whether all text is located in one contiguous chunk in memory.
|
||
*/
|
||
bool is_continuous() const {
|
||
if (chunks.size()) {
|
||
const K* ptr = chunks.front().first.get();
|
||
for (const auto& chunk: chunks) {
|
||
if (chunk.first.get() != ptr)
|
||
return false;
|
||
ptr += chunk.second;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
/*!
|
||
* @ru @brief Получить указатель на начало первого буфера.
|
||
* Имеет смысл применять только если is_continuous true.
|
||
* @en @brief Get a pointer to the beginning of the first buffer.
|
||
* It makes sense to apply only if is_continuous true.
|
||
*/
|
||
const K* begin() const {
|
||
return chunks.size() ? chunks.front().first.get() : simple_str_nt<K>::empty_str.str;
|
||
}
|
||
/*!
|
||
* @ru @brief Очистить объект, освободив все выделенные буфера.
|
||
* @en @brief Clear the object, freeing all allocated buffers.
|
||
*/
|
||
void clear() {
|
||
chunks.clear();
|
||
write = nullptr;
|
||
len = 0;
|
||
remain = 0;
|
||
}
|
||
/*!
|
||
* @ru @brief Объект, позволяющий последовательно копировать содержимое в буфер заданного размера.
|
||
* @en @brief An object that allows you to sequentially copy content into a buffer of a given size.
|
||
*/
|
||
struct portion_store {
|
||
typename decltype(chunks)::const_iterator it, end;
|
||
size_t writedFromCurrentChunk;
|
||
/*!
|
||
* @ru @brief Проверить, что данные ещё не кончились.
|
||
* @en @brief Check that the data has not yet run out.
|
||
*/
|
||
bool is_end() {
|
||
return it == end;
|
||
}
|
||
/*!
|
||
* @ru @brief Сохранить очередную порцию данных в буфер.
|
||
* @param buffer - указатель на буфер для сохранения данных.
|
||
* @param size - размер буфера.
|
||
* @return size_t - количество скопированных СИМВОЛОВ (не байтов).
|
||
* @en @brief Save the next portion of data to the buffer.
|
||
* @param buffer - pointer to the buffer for storing data.
|
||
* @param size - buffer size.
|
||
* @return size_t - the number of CHARACTERS (not bytes) copied.
|
||
*/
|
||
size_t store(K* buffer, size_t size) {
|
||
size_t writed = 0;
|
||
while (size && !is_end()) {
|
||
size_t remain = it->second - writedFromCurrentChunk;
|
||
size_t write = std::min(size, remain);
|
||
ch_traits<K>::copy(buffer, it->first.get() + writedFromCurrentChunk, write);
|
||
writed += write;
|
||
remain -= write;
|
||
size -= write;
|
||
if (!remain) {
|
||
++it;
|
||
writedFromCurrentChunk = 0;
|
||
} else
|
||
writedFromCurrentChunk += write;
|
||
}
|
||
return writed;
|
||
}
|
||
};
|
||
/*!
|
||
* @ru @brief Получить portion_store, через который можно последовательно извлекать данные во внешний буфер.
|
||
* @return portion_store.
|
||
* @en @brief Get a portion_store through which data can be sequentially retrieved into an external buffer.
|
||
* @return portion_store.
|
||
*/
|
||
portion_store get_portion() const {
|
||
return {chunks.begin(), chunks.end(), 0};
|
||
}
|
||
/*!
|
||
* @ru @brief Получить внутренние буфера с данными.
|
||
* @return const auto&.
|
||
* @en @brief Get internal data buffers.
|
||
* @return const auto&.
|
||
*/
|
||
const auto& data() const {
|
||
return chunks;
|
||
}
|
||
};
|
||
|
||
using stringa = sstring<u8s>;
|
||
using stringw = sstring<wchar_t>;
|
||
using stringu = sstring<u16s>;
|
||
using stringuu = sstring<u32s>;
|
||
static_assert(sizeof(stringa) == (sizeof(void*) == 8 ? 24 : 16), "Bad size of sstring");
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char строк, регистрозависимый поиск.
|
||
* @en @brief Type of hash dictionary for char strings, case sensitive search.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapA = hashStrMap<u8s, T, strhash<u8s>, streql<u8s>>;
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char строк, регистронезависимый поиск для ASCII символов.
|
||
* @en @brief Type of hash dictionary for char strings, case-insensitive lookup for ASCII characters.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapAIA = hashStrMap<u8s, T, strhashia<u8s>, streqlia<u8s>>;
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char строк, регистронезависимый поиск для Unicode символов до 0xFFFF.
|
||
* @en @brief Hash dictionary type for char strings, case-insensitive search for Unicode characters up to 0xFFFF.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapAIU = hashStrMap<u8s, T, strhashiu<u8s>, streqliu<u8s>>;
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для wchar_t строк, регистрозависимый поиск.
|
||
* @en @brief Hash dictionary type for wchar_t strings, case sensitive search.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapW = hashStrMap<wchar_t, T, strhash<wchar_t>, streql<wchar_t>>;
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для wchar_t строк, регистронезависимый поиск для ASCII символов.
|
||
* @en @brief Hash dictionary type for wchar_t strings, case-insensitive lookup for ASCII characters.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapWIA = hashStrMap<wchar_t, T, strhashia<wchar_t>, streqlia<wchar_t>>;
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для wchar_t строк, регистронезависимый поиск для Unicode символов до 0xFFFF.
|
||
* @en @brief Hash dictionary type for wchar_t strings, case insensitive search for Unicode characters up to 0xFFFF.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapWIU = hashStrMap<wchar_t, T, strhashiu<wchar_t>, streqliu<wchar_t>>;
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char16_t строк, регистрозависимый поиск.
|
||
* @en @brief Hash dictionary type for char16_t strings, case sensitive search.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapU = hashStrMap<u16s, T, strhash<u16s>, streql<u16s>>;
|
||
template<typename T>
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char16_t строк, регистронезависимый поиск для ASCII символов.
|
||
* @en @brief Hash dictionary type for char16_t strings, case-insensitive lookup for ASCII characters.
|
||
*/
|
||
using hashStrMapUIA = hashStrMap<u16s, T, strhashia<u16s>, streqlia<u16s>>;
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char16_t строк, регистронезависимый поиск для Unicode символов до 0xFFFF.
|
||
* @en @brief Hash dictionary type for char16_t strings, case insensitive search for Unicode characters up to 0xFFFF.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapUIU = hashStrMap<u16s, T, strhashiu<u16s>, streqliu<u16s>>;
|
||
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char32_t строк, регистрозависимый поиск.
|
||
* @en @brief Hash dictionary type for char32_t strings, case sensitive search.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapUU = hashStrMap<u32s, T, strhash<u32s>, streql<u32s>>;
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char32_t строк, регистронезависимый поиск для ASCII символов.
|
||
* @en @brief Hash dictionary type for char32_t strings, case-insensitive lookup for ASCII characters.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapUUIA = hashStrMap<u32s, T, strhashia<u32s>, streqlia<u32s>>;
|
||
/*!
|
||
* @ru @brief Тип хеш-словаря для char32_t строк, регистронезависимый поиск для Unicode символов до 0xFFFF.
|
||
* @en @brief Hash dictionary type for char32_t strings, case insensitive search for Unicode characters up to 0xFFFF.
|
||
*/
|
||
template<typename T>
|
||
using hashStrMapUUIU = hashStrMap<u32s, T, strhashiu<u32s>, streqliu<u32s>>;
|
||
|
||
inline constexpr simple_str_nt<u8s> utf8_bom{"\xEF\xBB\xBF", 3}; // NOLINT
|
||
|
||
inline namespace literals {
|
||
|
||
#ifdef _MSC_VER
|
||
/* MSVC иногда не может сделать "text"_ss consteval, выдает ошибку C7595.
|
||
Находил подобное https://developercommunity.visualstudio.com/t/User-defined-literals-not-constant-expre/10108165
|
||
Пишут, что баг исправлен, но видимо не до конца.
|
||
Без этого в тестах в двух местах не понимает "text"_ss, хотя в других местах - нормально работает*/
|
||
/* MSVC sometimes fails to do "text"_ss consteval and gives error C7595.
|
||
Found something like this https://developercommunity.visualstudio.com/t/User-defined-literals-not-constant-expre/10108165
|
||
They write that the bug has been fixed, but apparently not completely.
|
||
Without this, in tests in two places it does not understand “text”_ss, although in other places it works fine */
|
||
#define SS_CONSTEVAL constexpr
|
||
#else
|
||
#define SS_CONSTEVAL consteval
|
||
#endif
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в simple_str_nt.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return simple_str_nt.
|
||
* @en @brief Operator literal in simple_str_nt.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return simple_str_nt.
|
||
*/
|
||
SS_CONSTEVAL simple_str_nt<u8s> operator""_ss(const u8s* ptr, size_t l) {
|
||
return simple_str_nt<u8s>{ptr, l};
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор литерал в simple_str_nt.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return simple_str_nt.
|
||
* @en @brief Operator literal in simple_str_nt.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return simple_str_nt.
|
||
*/
|
||
SS_CONSTEVAL simple_str_nt<uws> operator""_ss(const uws* ptr, size_t l) {
|
||
return simple_str_nt<uws>{ptr, l};
|
||
}
|
||
/*!
|
||
* @ru @brief Оператор литерал в simple_str_nt.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return simple_str_nt.
|
||
* @en @brief Operator literal in simple_str_nt.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return simple_str_nt.
|
||
*/
|
||
SS_CONSTEVAL simple_str_nt<u16s> operator""_ss(const u16s* ptr, size_t l) {
|
||
return simple_str_nt<u16s>{ptr, l};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в simple_str_nt.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return simple_str_nt.
|
||
* @en @brief Operator literal in simple_str_nt.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return simple_str_nt.
|
||
*/
|
||
SS_CONSTEVAL simple_str_nt<u32s> operator""_ss(const u32s* ptr, size_t l) {
|
||
return simple_str_nt<u32s>{ptr, l};
|
||
}
|
||
|
||
template<typename K> using HashKey = StoreType<K, strhash<K>>;
|
||
template<typename K> using HashKeyIA = StoreType<K, strhashia<K>>;
|
||
template<typename K> using HashKeyIU = StoreType<K, strhashiu<K>>;
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем с учётом регистра.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-sensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKey<u8s> operator""_h(const u8s* ptr, size_t l) {
|
||
return HashKey<u8s>{{ptr, l}, fnv_hash_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра ASCII.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-insensitive ASCII hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKeyIA<u8s> operator""_ia(const u8s* ptr, size_t l) {
|
||
return HashKeyIA<u8s>{{ptr, l}, fnv_hash_ia_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра simple unicode.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a simple unicode case-insensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
inline HashKeyIU<u8s> operator""_iu(const u8s* ptr, size_t l) {
|
||
return HashKeyIU<u8s>{{ptr, l}, strhashiu<u8s>{}(simple_str<u8s>{ptr, l})};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем с учётом регистра.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-sensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKey<u16s> operator""_h(const u16s* ptr, size_t l) {
|
||
return HashKey<u16s>{{ptr, l}, fnv_hash_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра ASCII.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-insensitive ASCII hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKeyIA<u16s> operator""_ia(const u16s* ptr, size_t l) {
|
||
return HashKeyIA<u16s>{{ptr, l}, fnv_hash_ia_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра simple unicode.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a simple unicode case-insensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
inline HashKeyIU<u16s> operator""_iu(const u16s* ptr, size_t l) {
|
||
return HashKeyIU<u16s>{{ptr, l}, strhashiu<u16s>{}(simple_str<u16s>{ptr, l})};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем с учётом регистра.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-sensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKey<u32s> operator""_h(const u32s* ptr, size_t l) {
|
||
return HashKey<u32s>{{ptr, l}, fnv_hash_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра ASCII.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-insensitive ASCII hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKeyIA<u32s> operator""_ia(const u32s* ptr, size_t l) {
|
||
return HashKeyIA<u32s>{{ptr, l}, fnv_hash_ia_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра simple unicode.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a simple unicode case-insensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
inline HashKeyIU<u32s> operator""_iu(const u32s* ptr, size_t l) {
|
||
return HashKeyIU<u32s>{{ptr, l}, strhashiu<u32s>{}(simple_str<u32s>{ptr, l})};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем с учётом регистра.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-sensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKey<uws> operator""_h(const uws* ptr, size_t l) {
|
||
return HashKey<uws>{{ptr, l}, fnv_hash_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра ASCII.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a case-insensitive ASCII hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
consteval HashKeyIA<uws> operator""_ia(const uws* ptr, size_t l) {
|
||
return HashKeyIA<uws>{{ptr, l}, fnv_hash_ia_compile(ptr, l)};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор литерал в ключ для hashStrMap с посчитанным в compile time хешем без учёта регистра simple unicode.
|
||
* @param ptr - указатель на строку.
|
||
* @param l - длина строки.
|
||
* @return StoreType.
|
||
* @en @brief Key literal operator for hashStrMap with a simple unicode case-insensitive hash calculated at compile time.
|
||
* @param ptr - pointer to a string.
|
||
* @param l - string length.
|
||
* @return StoreType.
|
||
*/
|
||
inline HashKeyIU<uws> operator""_iu(const uws* ptr, size_t l) {
|
||
return HashKeyIU<uws>{{ptr, l}, strhashiu<uws>{}(simple_str<uws>{ptr, l})};
|
||
}
|
||
} // namespace literals
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток simple_str.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Stream output operator simple_str.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::ostream& operator<<(std::ostream& stream, ssa text) {
|
||
return stream << std::string_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток simple_str.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Stream output operator simple_str.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::wostream& operator<<(std::wostream& stream, ssw text) {
|
||
return stream << std::wstring_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток simple_str.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Stream output operator simple_str.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::wostream& operator<<(std::wostream& stream, simple_str<wchar_type> text) {
|
||
return stream << std::wstring_view{from_w(text.symbols()), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток sstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator for outputting sstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::ostream& operator<<(std::ostream& stream, const stringa& text) {
|
||
return stream << std::string_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток sstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator for outputting sstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::wostream& operator<<(std::wostream& stream, const stringw& text) {
|
||
return stream << std::wstring_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток sstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator for outputting sstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
inline std::wostream& operator<<(std::wostream& stream, const sstring<wchar_type>& text) {
|
||
return stream << std::wstring_view{from_w(text.symbols()), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток lstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator to output lstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
template<size_t N, bool S, simstr::Allocatorable A>
|
||
inline std::ostream& operator<<(std::ostream& stream, const lstring<u8s, N, S, A>& text) {
|
||
return stream << std::string_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток lstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator to output lstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
template<size_t N, bool S, simstr::Allocatorable A>
|
||
inline std::wostream& operator<<(std::wostream& stream, const lstring<uws, N, S, A>& text) {
|
||
return stream << std::wstring_view{text.symbols(), text.length()};
|
||
}
|
||
|
||
/*!
|
||
* @ru @brief Оператор вывода в поток lstring.
|
||
* @param stream - поток вывода.
|
||
* @param text - текст.
|
||
* @return std::ostream&.
|
||
* @en @brief Operator to output lstring to stream.
|
||
* @param stream - output stream.
|
||
* @param text - text.
|
||
* @return std::ostream&.
|
||
*/
|
||
template<size_t N, bool S, simstr::Allocatorable A>
|
||
inline std::wostream& operator<<(std::wostream& stream, const lstring<wchar_type, N, S, A>& text) {
|
||
return stream << std::wstring_view{from_w(text.symbols()), text.length()};
|
||
}
|
||
|
||
} // namespace simstr
|
||
|
||
/*!
|
||
* @ru @brief Форматтер для использования в std::format значений типа simple_str.
|
||
* @en @brief Formatter to use in std::format for values of type simple_str.
|
||
*/
|
||
template<typename K>
|
||
struct std::formatter<simstr::simple_str<K>, K> : std::formatter<std::basic_string_view<K>, K> {
|
||
// Define format() by calling the base class implementation with the wrapped value
|
||
template<typename FormatContext>
|
||
auto format(simstr::simple_str<K> t, FormatContext& fc) const {
|
||
return std::formatter<std::basic_string_view<K>, K>::format({t.str, t.len}, fc);
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Форматтер для использования в std::format значений типа simple_str_nt.
|
||
* @en @brief Formatter to use in std::format for values of type simple_str_nt.
|
||
*/
|
||
template<typename K>
|
||
struct std::formatter<simstr::simple_str_nt<K>, K> : std::formatter<std::basic_string_view<K>, K> {
|
||
// Define format() by calling the base class implementation with the wrapped value
|
||
template<typename FormatContext>
|
||
auto format(simstr::simple_str_nt<K> t, FormatContext& fc) const {
|
||
return std::formatter<std::basic_string_view<K>, K>::format({t.str, t.len}, fc);
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Форматтер для использования в std::format значений типа sstring.
|
||
* @en @brief Formatter to use in std::format for values of type string.
|
||
*/
|
||
template<typename K>
|
||
struct std::formatter<simstr::sstring<K>, K> : std::formatter<std::basic_string_view<K>, K> {
|
||
// Define format() by calling the base class implementation with the wrapped value
|
||
template<typename FormatContext>
|
||
auto format(const simstr::sstring<K>& t, FormatContext& fc) const {
|
||
return std::formatter<std::basic_string_view<K>, K>::format({t.symbols(), t.length()}, fc);
|
||
}
|
||
};
|
||
|
||
/*!
|
||
* @ru @brief Форматтер для использования в std::format значений типа lstring.
|
||
* @en @brief Formatter to use in std::format for values of type lstring.
|
||
*/
|
||
template<typename K, size_t N, bool S, typename A>
|
||
struct std::formatter<simstr::lstring<K, N, S, A>, K> : std::formatter<std::basic_string_view<K>, K> {
|
||
// Define format() by calling the base class implementation with the wrapped value
|
||
template<typename FormatContext>
|
||
auto format(const simstr::lstring<K, N, S, A>& t, FormatContext& fc) const {
|
||
return std::formatter<std::basic_string_view<K>, K>::format({t.symbols(), t.length()}, fc);
|
||
}
|
||
};
|