simstr/include/simstr/strexpr.h

7208 lines
359 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* ver. 1.6.5
* (c) Проект "SimStr", Александр Орефков orefkov@gmail.com
* База для строковых конкатенаций через выражения времени компиляции
* (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com
* Base for string concatenations via compile-time expressions
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <climits>
#include <limits>
#include <string>
#include <string_view>
#include <concepts>
#include <vector>
#include <optional>
#include <charconv>
#if defined __has_builtin
# if __has_builtin(__builtin_mul_overflow) && __has_builtin(__builtin_add_overflow)
# define HAS_BUILTIN_OVERFLOW
# endif
#endif
#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
#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 Пространство имён для объектов библиотеки
* @en @brief Library namespace
*/
namespace simstr {
// Выводим типы для 16 и 32 битных символов в зависимости от размера wchar_t
// Infer types for 16 and 32 bit characters depending on the size of wchar_t
inline constexpr bool wchar_is_u16 = sizeof(wchar_t) == sizeof(char16_t);
using wchar_type = std::conditional<wchar_is_u16, char16_t, char32_t>::type;
inline wchar_type* to_w(wchar_t* p) {
return (reinterpret_cast<wchar_type*>(p));
}
inline const wchar_type* to_w(const wchar_t* p) {
return (reinterpret_cast<const wchar_type*>(p));
}
inline wchar_t* from_w(wchar_type* p) {
return (reinterpret_cast<wchar_t*>(p));
}
inline const wchar_t* from_w(const wchar_type* p) {
return (reinterpret_cast<const wchar_t*>(p));
}
using u8s = char;
using ubs = char8_t;
using uws = wchar_t;
using u16s = char16_t;
using u32s = char32_t;
using uu8s = std::make_unsigned<u8s>::type;
template<typename T, typename K = void, typename... Types>
struct is_one_of_type {
static constexpr bool value = std::is_same_v<T, K> || is_one_of_type<T, Types...>::value;
};
template<typename T>
struct is_one_of_type<T, void> : std::false_type {};
template<typename K>
concept is_one_of_char_v = is_one_of_type<K, u8s, ubs, wchar_t, u16s, u32s>::value;
template<typename K>
concept is_one_of_std_char_v = is_one_of_type<K, u8s, ubs, wchar_t, wchar_type>::value;
template<is_one_of_std_char_v From>
auto to_one_of_std_char(From* from) {
if constexpr (std::is_same_v<From, u8s> || std::is_same_v<From, wchar_t>) {
return from;
} else if constexpr (std::is_same_v<From, ubs>) {
return reinterpret_cast<u8s*>(from);
} else {
return from_w(from);
}
}
template<is_one_of_std_char_v From>
auto to_one_of_std_char(const From* from) {
if constexpr (std::is_same_v<From, u8s> || std::is_same_v<From, wchar_t>) {
return from;
} else if constexpr (std::is_same_v<From, ubs>) {
return reinterpret_cast<const u8s*>(from);
} else {
return from_w(from);
}
}
/*!
* @ingroup StrExprs
* @ru @brief Проверка, являются ли два типа совместимыми строковыми типами.
* @tparam K1 - первый проверяемый тип.
* @tparam K2 - второй проверяемый тип.
* @details Оба типа должны быть строковыми типами и совпадать по размеру.
* То есть char и char8_t всегда совместимы, wchar_t тождественен в Linux char32_t, а в Windows - char16_t.
* Это для возможности смешивать строковые выражения совместимых типов.
* @en @brief Checks whether two types are compatible string types.
* @tparam K1 is the first type to check.
* @tparam K2 is the second type to check.
* @details Both types must be string types and the same size.
* That is, char and char8_t are always compatible, wchar_t is identical to char32_t on Linux, and char16_t on Windows.
* This is for the ability to mix string expressions of compatible types.
*/
template<typename K1, typename K2>
concept is_equal_str_type_v = sizeof(K1) == sizeof(K2) && is_one_of_char_v<K1> && is_one_of_char_v<K2>;
/*
Вспомогательные шаблоны для определения строковых литералов.
Используются для того, чтобы в параметрах функций ограничивать типы строго как `const K(&)[N]`
Если пишем
template<size_t N>
void func(const char(&lit)[N]);
то в такую функцию можно будет передать не константный буфер, что может вызывать ошибку:
// Выделили место под символы
char buf[100];
// Как то наполнили буфер, допустим, до половины.
stringa text = buf;
Тут компилятор приведет buf из типа char[100] в тип const char[100] и вызовет конструктор
для строкового литерала, в text запишется просто указатель на buf и длина 100.
Поэтому такие параметры объявляем как
template<typename T, typename K = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count>
void func(T&& lit);
Тогда компилятор будет подставлять для T точный тип параметра без попыток привести тип к другому типу,
и выражение с параметром char[100] - не скомпилируется.
Helper templates for defining string literals.
They are used to limit types in function parameters strictly as `const K(&)[N]`
If we write
template<size_t N>
void func(const char(&lit)[N]);
then it will be possible to pass a non-constant buffer to such a function, which may cause an error:
// Allocate space for symbols
char buf[100];
// Somehow the buffer was filled, say, to half.
stringa text = buf;
Here the compiler will convert buf from type char[100] to type const char[100] and call the constructor
for a string literal, text will simply contain a pointer to buf and length 100.
Therefore, we declare such parameters as
template<typename T, typename K = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count>
void func(T&& lit);
Then the compiler will substitute for T the exact type of the parameter without attempting to cast the type to another type,
and an expression with the char[100] parameter will not compile.
*/
template<typename T> struct const_lit; // sfinae отработает, так как не найдёт определения | sfinae will work because it won't find a definition
// Для правильных типов параметров есть определение, в виде специализации шаблона
// There is a definition for the correct parameter types, in the form of a template specialization
template<is_one_of_char_v T, size_t N>
struct const_lit<const T(&)[N]> {
using symb_type = T;
constexpr static size_t Count = N;
};
template<typename T>
concept is_const_lit_v = requires {
typename const_lit<T>::symb_type;
};
// Тут ещё дополнительно ограничиваем тип литерала
// Here we further restrict the type of the literal
template<typename K, typename T> struct const_lit_for;
template<typename K, is_equal_str_type_v<K> P, size_t N>
struct const_lit_for<K, const P(&)[N]> {
constexpr static size_t Count = N;
};
template<typename K, size_t N>
class const_lit_to_array {
template<size_t Idx>
constexpr size_t find(K s) const {
if constexpr (Idx < N) {
return s == symbols_[Idx] ? Idx : find<Idx + 1>(s);
}
return -1;
}
template<size_t Idx>
constexpr bool exist(K s) const {
if constexpr (Idx < N) {
return s == symbols_[Idx] || exist<Idx + 1>(s);
}
return false;
}
public:
const K (&symbols_)[N + 1];
template<typename T, size_t M = const_lit_for<K, T>::Count> requires (M == N + 1)
constexpr const_lit_to_array(T&& s)
: symbols_(s) {}
constexpr bool contain(K s) const {
return exist<0>(s);
}
constexpr size_t index_of(K s) const {
return find<0>(s);
}
};
template<typename A>
concept StrTypeCommon = requires(const A& a) {
typename std::remove_cvref_t<A>::symb_type;
{ a.is_empty() } -> std::same_as<bool>;
{ a.length() } -> std::convertible_to<size_t>;
{ a.symbols() };
};
template<typename A>
concept HasSymbType = requires {
typename std::remove_cvref_t<A>::symb_type;
};
/*!
* @ru @brief Базовая концепция строкового объекта.
* @tparam A - проверяемый тип
* @tparam K - тип символов
* @details В библиотеке для разных целей могут использоваться различные типы объектов строк.
* Мы считаем строковым объектом любой объект, поддерживающий методы:
* - `is_empty()`: возвращает, пуста ли строка.
* - `length()`: возвращает длину строки без нулевого терминатора.
* - `symbols()`: возвращает указатель на строку символов.
* - `typename symb_type`: задаёт тип символов строки
*
* @en @brief Base concept of string object.
* @tparam A - tested type
* @tparam K - type of symbols
* @details The library can use different types of string objects for different purposes.
* We consider a string object to be any object that supports methods:
* - `is_empty()`: Returns whether the string is empty.
* - `length()`: returns the length of a string without a null terminator.
* - `symbols()`: returns a pointer to a string of symbols.
* - `typename symb_type`: sets the character type of the string
*/
template<typename A, typename K>
concept StrType = StrTypeCommon<A> && requires(const A& a) {
{ a.symbols() } -> std::same_as<const K*>;
} && std::is_same_v<typename std::remove_cvref_t<A>::symb_type, K>;
template<typename T> struct is_std_string_source : std::false_type{};
template<typename K, typename A>
struct is_std_string_source<std::basic_string<K, std::char_traits<K>, A>> : std::true_type{};
template<typename K>
struct is_std_string_source<std::basic_string_view<K, std::char_traits<K>>> : std::true_type{};
template<typename T>
concept is_std_string_source_v = is_std_string_source<T>::value;
template<typename T>
concept StdStrSource = is_std_string_source_v<std::remove_cvref_t<T>>;
template<typename T, typename K>
concept StdStrSourceForType = StdStrSource<T> && is_equal_str_type_v<K, typename T::value_type>;
template<typename T>
concept StrSource = StdStrSource<T> || is_const_lit_v<T> || StrTypeCommon<T>;
template<typename T>
concept StrSourceNoLiteral = StdStrSource<T> || StrTypeCommon<T>;
template<typename T> struct is_std_string : std::false_type{};
template<typename K, typename A>
struct is_std_string<std::basic_string<K, std::char_traits<K>, A>> : std::true_type{};
template<typename T>
concept is_std_string_v = is_std_string<T>::value;
template<typename T, typename K>
concept StdStringForType = is_std_string_v<T> && is_equal_str_type_v<K, typename T::value_type>;
template<typename T>
struct symb_type_from_src {
using type = void;
};
template<typename K, size_t N>
struct symb_type_from_src<const K(&)[N]> {
using type = K;
};
template<StdStrSource T>
struct symb_type_from_src<T> {
using type = typename std::remove_cvref_t<T>::value_type;
};
template<HasSymbType T>
struct symb_type_from_src<T> {
using type = typename std::remove_cvref_t<T>::symb_type;
};
template<typename T>
using symb_type_from_src_t = symb_type_from_src<T>::type;
/*!
* @ru @defgroup StrExprs Строковые выражения
* @brief Описание строковых выражений
* @details Все типы владеющих строк в simstr могут инициализироваться с помощью "строковых выражений".
* (по сути это вариант https://en.wikipedia.org/wiki/Expression_templates для строк).
* Строковое выражение - это объект произвольного типа, у которого имеются методы:
* - `size_t length() const`: выдает длину строки
* - `K* place(K*) const`: скопировать символы строки в предназначенный буфер и вернуть указатель за последним символом
* - `typename symb_type`: показывает, с каким типом символов он работает.
*
* При инициализации строковый объект запрашивает у строкового выражения его размер, выделяет необходимую память,
* и передает память строковому выражению, которое помещает символы в отведённый буфер.
*
* Кроме того, для совместимости с `std`, строковые выражения simstr могут конвертироваться в стандартные строки
* (std::basic_string) совместимых типов. До C++23 используется `resize` и потом заполнение через `data()`, начиная с C++23
* используется более оптимальный `resize_and_overwrite`. Это позволяет использовать быструю конкатенацию там, где требуются стандартные строки.
*
* Все строковые объекты библиотеки сами являются строковыми выражениями, которые просто копирует исходную строку.
* В-основном строковые выражения используются для конкатенации или конвертации строк.
*
* Для всех строковых выражений определен @ref op_plus_str_expr "operator +", который из двух операндов создает новое строковое выражение
* simstr::strexprjoin, объединяющее два строковых выражения, и которое в методе `length` возвращает сумму `length` исходных операндов,
* а в методе `place` - размещает в буфер результата сначала первый операнд, потом второй.
* И так как этот оператор сам возвращает строковое выражение, то к нему снова можно применить `operator +`, формируя цепочку из нескольких
* строковых выражений, и в итоге "материализовать" последний получившийся объект, который сначала посчитает размер всей общей памяти
* для конечного результата, а затем разместит вложенные подвыражения в один буфер.
*
* Также `operator +` определён для строковых выражений и строковых литералов, строковых выражений и чисел
* (числа конвертируются в десятичное представление), а также вы можете сами добавить желаемые типы строковых выражений.
* Пример:
* ```cpp
* stringa text = header + ", count = " + count + ", done";
* ```
* Существует несколько типов строковых выражений "из коробки", для выполнения различных операций со строками
* - `expr_spaces< ТипСимвола, КоличествоСимволов, Символ>{}`: выдает строку длиной КоличествоСимволов,
* заполненную заданным символом. Количество символов и символ - константы времени компиляции.
* Для некоторых случаев есть сокращенная запись:
* - `e_spca< КоличествоСимволов>()`: строка char пробелов
* - `e_spcw< КоличествоСимволов>()`: строка w_char пробелов
* - `expr_pad< ТипСимвола>{КоличествоСимволов, Символ}`: выдает строку длинной КоличествоСимволов,
* заполненную заданным символом. Количество символов и символ могут задаваться в рантайме.
* Сокращенная запись: `e_c (КоличествоСимволов, Символ)`
* - `e_choice (bool Condition, StrExpr1, StrExpr2)`: если Condition == true, результат будет равен StrExpr1, иначе StrExpr2
* - `e_num< ТипСимвола>(ЦелоеЧисло)`: конвертирует число в десятичное представление. Редко используется, так как
* для строковых выражений и чисел переопределен оператор "+", и число можно просто написать как text + number;
* - `e_real< ТипСимвола>(ВещественноеЧисло)`: конвертирует число в десятичное представление. Редко используется, так как
* для строковых выражений и чисел переопределен оператор "+", и число можно просто написать как `text + number`;
* - `e_join< bool ПослеПоследнего = false, bool ПропускатьПустые = false>(контейнер, "Разделитель")`: конкатенирует все строки
* в контейнере, используя разделитель.
* Если `ПослеПоследнего == true`, то разделитель добавляется и после последнего элемента контейнера, иначе только
* между элементами.
* Если `ПропускатьПустые == true`, то пустые строки не добавляют разделитель, иначе для каждой пустой строки
* тоже вставляется разделитель
* - `e_repl(ИсходнаяСтрока, Искать, Заменять)`: заменяет в исходной строке вхождения Искать на Заменять.
* - `e_hex(Число)`: генерирует строку с 16ричным представлением числа.
* - `e_fill_left(StrExpr, width, symbol)`, `e_fill_right(StrExpr, width, symbol)`: дополняет строковое выражение до нужной длины заданным символом.
* и т.д. и т.п.
*
* В одно выражение могут объединятся строковые выражения для символов разных, но совместимых типов.
* То есть можно сочетать `char` и `char8_t`, под Linux `wchar_t` и `char32_t`, под Windows `wchar_t` и `char16_t`.
*
* Помните: строковое выражение - это не строка, это только "инструкция", как собрать строку.
*
* @en @defgroup StrExprs String Expressions
* @brief Description of String Expressions
* @details All owning string types can be initialized using "string expressions"
* (essentially a variant of https://en.wikipedia.org/wiki/Expression_templates for strings).
* A string expression is an object of an arbitrary type that has methods:
* - `size_t length() const`: returns the length of the string
* - `K* place(K*) const`: copy the characters of the string to the intended buffer and return a pointer behind the last character
* - `typename symb_type`: shows what type of symbols it works with.
*
* During initialization, a string object asks the string expression for its size, allocates the necessary memory,
* and passes the memory to a string expression that places the characters in the allocated buffer.
*
* Additionally, for compatibility with `std`, simstr string expressions can be converted to standard strings
* (std::basic_string) compatible types. Before C++23, `resize` and `data` is used, starting with C++23
* the more optimal `resize_and_overwrite` is used. This allows for fast concatenation where standard strings are required.
*
* All library string objects are themselves string expressions that simply copy the original string.
* Basically, string expressions are used to concatenate or convert strings.
*
* For all string expressions, @ref op_plus_str_expr "operator +" is defined, which creates a new string expression from two operands
* simstr::strexprjoin, which combines two string expressions, and which in the `length` method returns the sum of the `length` original operands,
* and in the `place` method - places first the first operand, then the second, into the result buffer.
* And since this operator itself returns a string expression, you can again apply `operator +` to it, forming a chain of several
* string expressions, and eventually “materialize” the last resulting object, which will first calculate the size of the entire shared memory
* for the final result, and then will place the nested subexpressions into a single buffer.
*
* Also `operator +` is defined for string expressions and string literals, string expressions and numbers
* (numbers are converted to decimal representation), and you can also add the desired types of string expressions yourself.
* Example:
* ```cpp
* stringa text = header + ", count = " + count + ", done";
* ```
* There are several types of string expressions out of the box to perform various operations on strings
* - `expr_spaces< Character Type, Number of Characters, Symbol>{}`: returns a string of length Number of Characters,
* filled with the specified character. The number of characters and character are compile-time constants.
* For some cases there is a shorthand notation:
* - `e_spca< Number of Characters>()`: char string of spaces
* - `e_spcw< Number of Characters>()`: w_char string of spaces
* - `expr_pad< Character Type>{Number of Characters, Symbol}`: produces a string long Number of Characters,
* filled with the specified character. The number of characters and the symbol can be set at runtime.
* Shorthand: `e_c(Number of Characters, Character)`
* - `e_choice(bool Condition, StrExpr1, StrExpr2)`: if Condition == true, the result will be StrExpr1, otherwise StrExpr2
* - `e_num< CharacterType>(IntegerNumber)`: Converts a number to decimal notation. Rarely used because
* for string expressions and numbers the "+" operator is redefined, and the number can simply be written as text + number;
* - `e_real< CharacterType>(RealNumber)`: Converts a number to decimal notation. Rarely used because
* for string expressions and numbers the "+" operator is overridden, and the number can simply be written as `text + number`;
* - `e_join< bool AfterLast = false, bool SkipEmpty = false>(container, "Separator")`: concatenates all strings
* in a container using a separator.
* If `AfterLast == true`, then the separator is added after the last element of the container, otherwise only
* between elements.
* If `Skip Empty == true`, then empty lines do not add a separator, otherwise for each empty line
* a separator is also inserted
* - `e_repl(SourceString, Search, Replace)`: replaces the occurrences of Search with Replace in the source string.
* - `e_hex(Number)`: generates a string with hexadecimal representation of the number.
* - `e_fill_left(StrExpr, width, symbol)`, `e_fill_right(StrExpr, width, symbol)`: fills a string expression to the required length with a given character.
* etc. etc.
*
* String expressions for characters of different but compatible types can be combined into one expression.
* That is, you can combine `char` and `char8_t`, and under Linux `wchar_t` and `char32_t`, under Windows `wchar_t` and `char16_t`.
*
* Remember: a string expression is not a string, it is only an "instruction" on how to assemble the string.
*/
/*!
* @ingroup StrExprs
* @ru @brief Концепт "Строковых выражений".
* @details Это концепт, проверяющий, является ли тип "строковым выражением".
* @en @brief Concept of "String Expressions".
* @details This is a concept that checks whether a type is a "string expression".
*/
template<typename A>
concept StrExpr = requires(const A& a) {
typename std::remove_cvref_t<A>::symb_type;
{ a.length() } -> std::convertible_to<size_t>;
{ a.place(std::declval<typename std::remove_cvref_t<A>::symb_type*>()) } -> std::same_as<typename std::remove_cvref_t<A>::symb_type*>;
};
/*!
* @ingroup StrExprs
* @ru @brief Концепт строкового выражения, совместимого с заданным типом символов.
* @tparam A - проверяемый тип.
* @tparam K - проверяемый тип символов.
* @details Служит для задания ограничения к строковому выражению по типу символов.
* @en @brief The concept of a string expression compatible with a given character type.
* @tparam A - type being checked.
* @tparam K - character type to be checked.
* @details Used to set restrictions on a string expression by character type.
*/
template<typename A, typename K>
concept StrExprForType = StrExpr<A> && is_equal_str_type_v<K, typename std::remove_cvref_t<A>::symb_type>;
template<typename K, typename T>
struct convert_to_strexpr;
template<typename A, typename K>
concept strexpr_from = requires(const A& a) {
{convert_to_strexpr<K, std::remove_cvref_t<A>>::convert(a)} -> StrExprForType<K>;
};
template<typename A, typename K>
concept strexpr_std = requires(const A& a) {
{convert_to_strexpr<K, std::remove_cvref_t<A>>::convert(a)} -> StdStringForType<K>;
};
template<typename A, typename K>
concept strexpr_for = StrExprForType<typename convert_to_strexpr<K, std::remove_cvref_t<A>>::type, K>;
template<typename A, typename K>
concept to_strexpr_type = StrExprForType<typename std::remove_cvref_t<A>::strexpr, K>;
template<typename A, typename K>
concept to_strexpr_meth = requires(const A& a) {
{a.template to_strexpr<K>()} -> StrExprForType<K>;
};
template<typename A, typename K>
concept to_strexpr_std = requires(const A& a) {
{a.template to_strexpr<K>()} -> StdStringForType<K>;
};
template<typename A, typename K>
concept convertible_to_strexpr = strexpr_for<A, K> || strexpr_from<A, K> || strexpr_std<A, K> || to_strexpr_type<A, K> || to_strexpr_meth<A, K> || to_strexpr_std<A, K>;
template<typename K, strexpr_from<K> T>
constexpr auto to_strexpr(T&& t) {
return convert_to_strexpr<K, std::remove_cvref_t<T>>::convert(std::forward<T>(t));
}
template<typename K, strexpr_for<K> T>
constexpr typename convert_to_strexpr<K, std::remove_cvref_t<T>>::type to_strexpr(T&& t) {
return {std::forward<T>(t)};
}
template<typename K, to_strexpr_type<K> T>
constexpr typename std::remove_cvref_t<T>::strexpr to_strexpr(T&& t) {
return {std::forward<T>(t)};
}
template<typename K, to_strexpr_meth<K> T>
constexpr auto to_strexpr(T&& t) {
return t.template to_strexpr<K>();
}
template<typename K, StdStrSource T> requires is_equal_str_type_v<K, typename T::value_type>
struct expr_stdstr_c {
using symb_type = K;
T t_;
expr_stdstr_c(T t) : t_(std::move(t)){}
constexpr size_t length() const noexcept {
return t_.length();
}
constexpr symb_type* place(symb_type* p) const noexcept {
size_t s = t_.size();
std::char_traits<K>::copy(p, (const K*)t_.data(), s);
return p + s;
}
};
template<typename K, strexpr_std<K> T>
constexpr auto to_strexpr(T&& t) {
using type = decltype(convert_to_strexpr<K, std::remove_cvref_t<T>>::convert(std::forward<T>(t)));
return expr_stdstr_c<K, type>{convert_to_strexpr<K, std::remove_cvref_t<T>>::convert(std::forward<T>(t))};
}
template<typename K, to_strexpr_std<K> T>
constexpr auto to_strexpr(T&& t) {
using type = decltype(t.template to_strexpr<K>());
return expr_stdstr_c<K, type>{t.template to_strexpr<K>()};
}
template<typename K, typename T>
using convert_to_strexpr_t = decltype(to_strexpr<K>(std::declval<T>()));
/*
* Шаблонные классы для создания строковых выражений из нескольких источников.
* Благодаря компиляторно-шаблонной "магии" позволяют максимально эффективно
* получать результирующую строку - сначала вычисляется длина результирующей строки,
* потом один раз выделяется память для результата, после символы помещаются в
* выделенную память.
* Для конкатенация двух объектов строковых выражений в один
* Template classes for creating string expressions from multiple sources.
* Thanks to compiler-template "magic" they allow you to maximize efficiency
* get the resulting string - first the length of the resulting string is calculated,
* then memory is allocated once for the result, after which the characters are placed in
* allocated memory.
* For concatenating two string expression objects into one.
*/
template<typename K, typename Allocator, StrExpr A>
constexpr std::basic_string<K, std::char_traits<K>, Allocator> to_std_string(const A& expr) {
std::basic_string<K, std::char_traits<K>, Allocator> res;
if (size_t l = expr.length()) {
auto fill = [&](K* ptr, size_t size) -> size_t {
expr.place((typename A::symb_type*)ptr);
return l;
};
if constexpr (requires { res.resize_and_overwrite(l, fill); }) {
res.resize_and_overwrite(l, fill);
} else if constexpr (requires{ res._Resize_and_overwrite(l, fill); }) {
// Work in MSVC std lib before C++23
res._Resize_and_overwrite(l, fill);
} else {
res.resize(l); // bad, fill by 0 first.
expr.place((typename A::symb_type*)res.data());
}
}
return res;
}
/*!
* @ru @brief Базовый класс для преобразования строковых выражений в стандартные строки
* @details Если хотите, чтобы ваш тип строкового выражения конвертировался в стандартную строку,
* наследуйтесь от этого класса.
* @tparam Impl - конечный класс-наследник, для CRTP.
* @en @brief Base class for converting string expressions to standard strings
* @details If you want your string expression type to be converted to a standard string,
* inherit from this class.
* @tparam Impl - final descendant class for CRTP.
*/
template<typename Impl>
struct expr_to_std_string {
template<is_equal_str_type_v<typename Impl::symb_type> P, typename Allocator>
constexpr operator std::basic_string<P, std::char_traits<P>, Allocator>() const {
return to_std_string<P, Allocator>(*static_cast<const Impl*>(this));
}
};
/*!
* @ingroup StrExprs
* @ru @brief Шаблонный класс для конкатенации двух строковых выражений в одно с помощью `operator +`
* @tparam A - Тип первого операнда
* @tparam B - Тип второго операнда
* @details Этот объект запоминает ссылки на два операнда операции сложения.
* Когда у него запрашивают необходимый для результата размер буфера - он выдает сумму длин своих операндов.
* Когда запрашивают размещение символов в буфере - размещает сначала первый операнд, затем второй.
* @en @brief Template class for concatenating two string expressions into one using `operator +`
* @tparam A - Type of first operand
* @tparam B - Type of second operand
* @details This object remembers references to the two operands of the addition operation.
* When asked for the required buffer size for a result, it gives the sum of the lengths of its operands.
* When asked to place characters in a buffer, place the first operand first, then the second.
*/
template<StrExpr A, StrExprForType<typename A::symb_type> B>
struct strexprjoin : expr_to_std_string<strexprjoin<A, B>>{
using symb_type = typename A::symb_type;
const A& a;
const B& b;
constexpr strexprjoin(const A& a_, const B& b_) : a(a_), b(b_){}
constexpr size_t length() const noexcept {
return a.length() + b.length();
}
constexpr symb_type* place(symb_type* p) const noexcept {
return (symb_type*)b.place((typename B::symb_type*)a.place(p));
}
};
/*!
* @ingroup StrExprs
* @anchor op_plus_str_expr
* @ru @brief Оператор сложения двух произвольных строковых выражения для одинакового типа символов.
* @param a - первое строковое выражение.
* @param b - второе строковое выражение.
* @return strexprjoin<A, B>, строковое выражение, генерирующее объединение переданных выражений.
* @details Когда складываются два объекта - строковых выражения, один типа `A`, другой типа `B`,
* мы возвращаем объект типа strexprjoin<A, B>, который содержит ссылки на два этих операнда.
* А сам объект strexprjoin<A, B> тоже в свою очередь является строковым выражением, и может участвовать
* в следующих операциях сложения. Таким образом формируется "дерево" из исходных строковых
* выражений, которое потом за один вызов "материализуется" в конечный результат.
*
* @en @brief An addition operator for two arbitrary string expressions of the same character type.
* @param a - first string expression
* @param b - second string expression
* @return strexprjoin<A, B>, a string expression that generates a join of the given expressions.
* @details When two objects are added - string expressions, one of type `A`, the other of type `B`,
* we return an object of type strexprjoin<A, B>, which contains references to these two operands.
* And the strexprjoin<A, B> object itself, in turn, is also a string expression, and can participate
* in the following addition operations. In this way, a “tree” is formed from the original strings
* expressions, which are then “materialized” into the final result in one call.
*/
template<StrExpr A, StrExprForType<typename A::symb_type> B>
constexpr strexprjoin<A, B> operator+(const A& a, const B& b) {
return {a, b};
}
/*!
* @ingroup StrExprs
* @ru @brief Конкатенация ссылки на строковое выражение и значения строкового выражения.
* @tparam A - Тип одного строкового выражения.
* @tparam B - Тип другого строкового выражения.
* @tparam last - какое из них первое.
* @details Чтобы иметь возможность складывать строковое выражение с операндами, не являющимися строковым выражением,
* нам нужно иметь возможность вернуть из `operator+` объект, который сохранит ссылку на операнд, являющийся строковым
* выражением, а для не строкового операнда будет иметь поле со строковым выражением, обрабатывающим второй операнд.
* Можно посмотреть пример в simstr::operator+<StrExpr A, FromIntNumber T>()
*
* @en @brief Concatenation of a reference to a string expression and the value of the string expression.
* @tparam A - Type of a single string expression.
* @tparam B - Type of another string expression.
* @tparam last - which one is the first.
* @details To be able to add a string expression with non-string operands,
* we need to be able to return an object from `operator+` that will retain a reference to the operand, which is a string
* expression, and for a non-string operand will have a field with a string expression that processes the second operand.
* You can see an example in simstr::operator+<StrExpr A, FromIntNumber T>()
*/
template<StrExpr A, StrExprForType<typename A::symb_type> B, bool last = true>
struct strexprjoin_c : expr_to_std_string<strexprjoin_c<A, B, last>>{
using symb_type = typename A::symb_type;
const A& a;
B b;
template<typename... Args>
constexpr strexprjoin_c(const A& a_, Args&&... args_) : a(a_), b(std::forward<Args>(args_)...) {}
constexpr size_t length() const noexcept {
return a.length() + b.length();
}
constexpr symb_type* place(symb_type* p) const noexcept {
if constexpr (last) {
return (symb_type*)b.place((typename B::symb_type*)a.place(p));
} else {
return a.place((symb_type*)b.place((typename B::symb_type*)p));
}
}
};
/*!
* @ru @brief Оператор сложения строкового выражения и типов, для которых есть преобразование в строковое выражение.
* @tparam A - тип строкового выражения.
* @tparam B - тип слагаемого.
* @param a - строковое выражение.
* @param b - слагаемое.
* @return constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, true>
* @en @brief Addition operator for string expressions and types for which there is a conversion to string expressions.
* @tparam A - the type of the string expression.
* @tparam B - the type of the addend.
* @param a - the string expression.
* @param b - the addend.
* @return constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, true>
*/
template<StrExpr A, convertible_to_strexpr<typename A::symb_type> B>
inline constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, true> operator+(const A& a, B&& b) {
return {a, to_strexpr<typename A::symb_type>(std::forward<B>(b))};
}
/*!
* @ru @brief Оператор сложения типов, для которых есть преобразование в строковое выражение и строкового выражения.
* @tparam A - тип строкового выражения.
* @tparam B - тип слагаемого.
* @param b - слагаемое.
* @param a - строковое выражение.
* @return constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, true>
* @en @brief Addition operator for types for which there is a conversion to string expressions and string expressions.
* @tparam A - the type of the string expression.
* @tparam B - the type of the addend.
* @param b - the addend.
* @param a - the string expression.
* @return constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, false>
*/
template<StrExpr A, convertible_to_strexpr<typename A::symb_type> B>
inline constexpr strexprjoin_c<A, convert_to_strexpr_t<typename A::symb_type, B>, false> operator+(B&& b, const A& a) {
return {a, to_strexpr<typename A::symb_type>(std::forward<B>(b))};
}
/*!
* @ingroup StrExprs
* @ru @brief "Пустое" строковое выражение.
* @tparam K - тип символа.
* @details Простое строковое выражение, генерирующее пустую строку.
* В основном применяется в функции e_choice, когда одна из веток должна вернуть пустую строку.
* Либо для начала операции сложения строковых выражений, когда другой операнд не является строковым выражением,
* но для него есть оператор сложения со строковыми выражениями.
* Для удобства уже определены константные объекты этого типа для разных видов символов:
* - eea для пустой строки char
* - eew для пустой строки wchar_t
* - eeu для пустой строки char16_t
* - eeuu для пустой строки char32_t
*
* @en @brief An "empty" string expression.
* @tparam K is a symbol.
* @details A simple string expression that generates an empty string.
* Mainly used in the e_choice function when one of the branches should return an empty string.
* Either to start the addition operation of string expressions when the other operand is not a string expression,
* but there is an addition operator for it with string expressions.
* For convenience, constant objects of this type have already been defined for different types of symbols:
* - eea for empty char string
* - eew for empty string wchar_t
* - eeu for empty string char16_t
* - eeuu for empty string char32_t
*
*
* @ru Пример: @en Example: @~
* ```cpp
* // строковые литералы и числа не являются строковыми выражениями, нам надо с чего то начать операцию сложения со строковым выражением
* // string literals and numbers are not string expressions, we need to start the addition operation with a string expression somewhere
* result = eea + "Count is " + count;
* ```
*/
template<typename K>
struct empty_expr : expr_to_std_string<empty_expr<K>>{
using symb_type = K;
constexpr size_t length() const noexcept {
return 0;
}
constexpr symb_type* place(symb_type* p) const noexcept {
return p;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Пустое строковое выражение типа char.
* @en @brief Empty string expression of type char.
*/
inline constexpr empty_expr<u8s> eea{};
/*!
* @ingroup StrExprs
* @ru @brief Пустое строковое выражение типа char8_t.
* @en @brief Empty string expression of type char8_t.
*/
inline constexpr empty_expr<u8s> eeb{};
/*!
* @ingroup StrExprs
* @ru @brief Пустое строковое выражение типа wchar_t.
* @en @brief Empty string expression of type wchar_t.
*/
inline constexpr empty_expr<uws> eew{};
/*!
* @ingroup StrExprs
* @ru @brief Пустое строковое выражение типа char16_t.
* @en @brief Empty string expression of type char16_t.
*/
inline constexpr empty_expr<u16s> eeu{};
/*!
* @ingroup StrExprs
* @ru @brief Пустое строковое выражение типа char32_t.
* @en @brief Empty string expression of type char32_t.
*/
inline constexpr empty_expr<u32s> eeuu{};
template<typename K>
struct expr_char : expr_to_std_string<expr_char<K>>{
using symb_type = K;
K value;
constexpr expr_char(K v) : value(v){}
constexpr size_t length() const noexcept {
return 1;
}
constexpr symb_type* place(symb_type* p) const noexcept {
*p++ = value;
return p;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Оператор сложения строкового выражения и одного символа.
* @return строковое выражение, объединяющее переданное выражение и символ.
* @en @brief Addition operator of a string expression and one character.
* @return a string expression that combines the passed expression and a character.
* @details @ru Пример: @en Example: @~
* @~
* ```cpp
* reply = prompt + '>' + result;
* ```
*/
template<typename K, StrExprForType<K> A>
constexpr strexprjoin_c<A, expr_char<K>> operator+(const A& a, K s) {
return {a, s};
}
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из 1 заданного символа.
* @param s - символ.
* @return строковое выражение для строки из одного символа.
*
* @en @brief Generates a string of 1 given character.
* @param s - symbol.
* @return string expression for a single character string.
*/
template<typename K>
constexpr expr_char<K> e_char(K s) {
return {s};
}
template<typename K, size_t N>
struct expr_literal : expr_to_std_string<expr_literal<K, N>> {
using symb_type = K;
const K (&str)[N + 1];
constexpr expr_literal(const K (&str_)[N + 1]) : str(str_){}
constexpr size_t length() const noexcept {
return N;
}
constexpr symb_type* place(symb_type* p) const noexcept {
if constexpr (N != 0)
std::char_traits<K>::copy(p, str, N);
return p + N;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Преобразует строковый литерал в строковое выражение.
* @details Строковые литералы сами по себе не являются строковыми выражениями.
* Обычно в операциях конкатенации это не вызывает проблем, так как второй операнд уже является строковым выражением,
* и для него срабатывает сложение с литералом. Но есть ситуации, когда второй операнд тоже не является
* строковым выражением. Например:
* ```cpp
* int intVar = calculate();
* ...
* res = "text" + intVar;
* ...
* res = intVar + "text";
* ```
* В этом случае можно преобразовать литерал в строковое выражение двумя способами:
* - дописать _ss: `"text"_ss`, что преобразует литерал в simple_str_nt: `res = "text"_ss + intVar`
* - применить e_t: `e_t("text")`, что преобразует литерал в expr_literal: `res = e_t("text") + intVar`
*
* Во втором способе компилятор может более агрессивно применить оптимизации, связанные с известным при компиляции
* размером литерала.
*
* Хотя строго говоря, в этих ситуации можно пользоваться и другими способами:
* - Добавить операнд - пустое строковое выражение: `result = eea + "text" + intVar`, `result = "text" + eea + intVar`
* - Преобразовать другой операнд в строковое выражение: `result = "text" + e_num<u8s>(intVar)`.
*
* Все эти способы работают и выдают одинаковый результат. Каким пользоваться - дело вкуса.
*
* @en @brief Converts a string literal to a string expression.
* @details String literals are not themselves string expressions.
* This usually does not cause problems in concatenation operations, since the second operand is already a string expression,
* and addition with a literal works for it. But there are situations when the second operand is not either
* string expression. For example:
* ```cpp
* int intVar = calculate();
* ...
* res = "text" + intVar;
* ...
* res = intVar + "text";
* ```
* In this case, you can convert the literal to a string expression in two ways:
* - add _ss: `"text"_ss`, which converts the literal to simple_str_nt: `res = "text"_ss + intVar`
* - apply e_t: `e_t("text")`, which converts the literal to expr_literal: `res = e_t("text") + intVar`
*
* In the second method, the compiler can more aggressively apply optimizations related to what is known at compilation
* literal size.
*
* Although strictly speaking, in these situations you can use other methods:
* - Add an operand - an empty string expression: `result = eea + "text" + intVar`, `result = "text" + eea + intVar`
* - Convert another operand to a string expression: `result = "text" + e_num<u8s>(intVar)`.
*
* All these methods work and give the same result. Which one to use is a matter of taste.
*/
template<typename T, size_t N = const_lit<T>::Count>
constexpr expr_literal<typename const_lit<T>::symb_type, static_cast<size_t>(N - 1)> e_t(T&& s) {
return {s};
}
template<bool first, typename K, size_t N, typename A>
struct expr_literal_join : expr_to_std_string<expr_literal_join<first, K, N, A>> {
using symb_type = K;
using atype = typename A::symb_type;
const K (&str)[N + 1];
const A& a;
constexpr expr_literal_join(const K (&str_)[N + 1], const A& a_) : str(str_), a(a_){}
constexpr size_t length() const noexcept {
return N + a.length();
}
constexpr symb_type* place(symb_type* p) const noexcept {
if constexpr (N != 0) {
if constexpr (first) {
std::char_traits<K>::copy(p, str, N);
return (symb_type*)a.place((atype*)(p + N));
} else {
p = (symb_type*)a.place((atype*)p);
std::char_traits<K>::copy(p, str, N);
return p + N;
}
} else {
return a.place(p);
}
}
};
/*!
* @ingroup StrExprs
* @ru @brief Оператор сложения для строкового выражения и строкового литерала такого же типа символов.
* @return Строковое выражение, объединяющее операнды.
* @en @brief The addition operator for a string expression and a string literal of the same character type.
* @return A string expression concatenating the operands.
*/
template<StrExpr A, typename T, typename P = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count> requires is_equal_str_type_v<typename A::symb_type, P>
constexpr expr_literal_join<false, P, (N - 1), A> operator+(const A& a, T&& s) {
return {s, a};
}
/*!
* @ingroup StrExprs
* @ru @brief Оператор сложения для строкового литерала такого же типа символов и строкового выражения.
* @return Строковое выражение, объединяющее операнды.
* @en @brief The addition operator for a string literal of the same character type and string expression.
* @return A string expression concatenating the operands.
*/
template<StrExpr A, typename T, typename P = typename const_lit<T>::symb_type, size_t N = const_lit<T>::Count> requires is_equal_str_type_v<typename A::symb_type, P>
constexpr expr_literal_join<true, P, (N - 1), A> operator+(T&& s, const A& a) {
return {s, a};
}
/*!
* @ingroup StrExprs
* @ru @brief Тип строкового выражения, возвращающего N заданных символов.
* @details Количество символов и сам символ константы, т.е. задаются при компиляции.
* @tparam K - тип символа.
* @tparam N - количество символов.
* @tparam S - символ, по умолчанию пробел.
* @en @brief A type of string expression that returns N specified characters.
* @details The number of characters and the constant symbol itself, i.e. are specified during compilation.
* @tparam K is a symbol.
* @tparam N - number of characters.
* @tparam S - character, space by default.
*/
template<typename K, size_t N, size_t S = ' '>
struct expr_spaces : expr_to_std_string<expr_spaces<K, N>> {
using symb_type = K;
constexpr size_t length() const noexcept {
return N;
}
constexpr symb_type* place(symb_type* p) const noexcept {
if constexpr (N != 0)
std::char_traits<K>::assign(p, N, static_cast<K>(S));
return p + N;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из N char пробелов.
* @tparam N - Количество пробелов.
* @return строковое выражение для N char пробелов.
* @en @brief Generates a string of N char spaces.
* @tparam N - Number of spaces.
* @return string expression for N char spaces.
* @details @ru Пример: @en Example: @~
* ```cpp
* stringa text = e_spca<10>() + text + e_spca<10>();
* ```
*/
template<size_t N>
constexpr expr_spaces<u8s, N> e_spca() {
return {};
}
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из N wchar_t пробелов.
* @tparam N - Количество пробелов.
* @return строковое выражение для N wchar_t пробелов.
* @en @brief Generates a string of N wchar_t spaces.
* @tparam N - Number of spaces.
* @return string expression for N wchar_t spaces.
* @~ @details @ru Пример: @en Example: @~
* ```cpp
* stringw text = e_spcw<10>() + text + e_spcw<10>();
* ```
*/
template<size_t N>
constexpr expr_spaces<uws, N> e_spcw() {
return {};
}
/*!
* @ingroup StrExprs
* @ru @brief Тип строкового выражения, возвращающего N заданных символов.
* @tparam K - тип символа.
* @details Количество символов и сам символ переменные, т.е. могут меняться в рантайм.
* Напрямую обычно не используется, создается через e_c().
* @en @brief A type of string expression that returns N specified characters.
* @tparam K is a symbol.
* @details The number of characters and the character itself are variable, i.e. can change at runtime.
* Usually not used directly, created via e_c().
*/
template<typename K>
struct expr_pad : expr_to_std_string<expr_pad<K>> {
using symb_type = K;
size_t len;
K s;
constexpr expr_pad(size_t len_, K s_) : len(len_), s(s_){}
constexpr size_t length() const noexcept {
return len;
}
constexpr symb_type* place(symb_type* p) const noexcept {
if (len)
std::char_traits<K>::assign(p, len, s);
return p + len;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из l символов s типа K.
* @tparam K - тип символа.
* @param l - количество символов.
* @param s - символ.
* @return строковое выражение, генерирующее строку из l символов k.
* @en @brief Generates a string of l characters s of type K.
* @tparam K is a symbol.
* @param l - number of characters.
* @param s - symbol.
* @return a string expression that generates a string of l characters k.
*/
template<typename K>
constexpr expr_pad<K> e_c(size_t l, K s) {
return { l, s };
}
template<typename K, size_t N>
struct expr_repeat_lit : expr_to_std_string<expr_repeat_lit<K, N>> {
using symb_type = K;
size_t repeat_;
const K (&s)[N + 1];
constexpr expr_repeat_lit(size_t repeat, const K (&s_)[N + 1]) : repeat_(repeat), s(s_){}
constexpr size_t length() const noexcept {
return N * repeat_;
}
constexpr symb_type* place(symb_type* p) const noexcept {
if constexpr (N) {
for (size_t i = 0; i < repeat_; i++) {
std::char_traits<K>::copy(p, s, N);
p += N;
}
}
return p;
}
};
template<StrExpr A>
struct expr_repeat_expr : expr_to_std_string<expr_repeat_expr<A>> {
using symb_type = typename A::symb_type;
size_t repeat_;
const A& expr_;
constexpr expr_repeat_expr(size_t repeat, const A& expr) : repeat_(repeat), expr_(expr){}
constexpr size_t length() const noexcept {
if (repeat_) {
return repeat_ * expr_.length();
}
return 0;
}
constexpr symb_type* place(symb_type* p) const noexcept {
if (repeat_) {
if (repeat_ == 1) {
return expr_.place(p);
}
symb_type* start = p;
p = expr_.place(p);
size_t len = size_t(p - start);
if (len) {
for (size_t i = 1; i < repeat_; i++) {
std::char_traits<symb_type>::copy(p, start, len);
p += len;
}
}
}
return p;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из l строковых констант s типа K
* @tparam K - тип символа
* @param l - количество повторов
* @param s - строковый литерал
* @return строковое выражение, генерирующее строку из l строк s
* @en @brief Generate a string from l string constants s of type K
* @tparam K - character type
* @param l - number of repetitions
* @param s - string literal
* @return a string expression generating a string of l strings s
*/
template<typename T, typename K = const_lit<T>::symb_type, size_t M = const_lit<T>::Count> requires (M > 0)
constexpr expr_repeat_lit<K, M - 1> e_repeat(T&& s, size_t l) {
return { l, s };
}
/*!
* @ingroup StrExprs
* @ru @brief Генерирует строку из l строковых выражений s типа K
* @tparam K - тип символа
* @param l - количество повторов
* @param s - строковое выражение
* @return строковое выражение, генерирующее строку из l строковых выражений s
* @en @brief Generate a string from l string expressions s of type K
* @tparam K - character type
* @param l - number of repetitions
* @param s - string expression
* @return a string expression generating a string of l string expressions s
*/
template<StrExpr A>
constexpr expr_repeat_expr<A> e_repeat(const A& s, size_t l) {
return { l, s };
}
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение условного выбора.
* @tparam A Тип ветки для true.
* @tparam B Тип ветки для false.
* @details Выражение, в зависимости от истинности условия генерирующее либо выражение A, либо выражение B.
* Напрямую тип обычно не используется, создаётся через e_choice().
* @en @brief Conditional selection string expression.
* @tparam A Branch type for true.
* @tparam B Branch type for false.
* @details An expression that, depending on the truth of the condition, generates either expression A or expression B.
* The type is usually not used directly; it is created via e_choice().
*/
template<StrExpr A, StrExprForType<typename A::symb_type> B>
struct expr_choice : expr_to_std_string<expr_choice<A, B>> {
using symb_type = typename A::symb_type;
using my_type = expr_choice<A, B>;
const A& a;
const B& b;
bool choice;
constexpr expr_choice(const A& _a, const B& _b, bool _choice) : a(_a), b(_b), choice(_choice){}
constexpr size_t length() const noexcept {
return choice ? a.length() : b.length();
}
constexpr symb_type* place(symb_type* ptr) const noexcept {
return choice ? a.place(ptr) : (symb_type*)b.place((typename B::symb_type*)ptr);
}
};
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение условного выбора.
* @tparam A Тип ветки для true.
* @details Выражение, в зависимости от истинности условия генерирующее либо выражение A, либо пустую строку.
* Напрямую тип обычно не используется, создаётся через e_if().
* @en @brief Conditional selection string expression.
* @tparam A Branch type for true.
* @details An expression that, depending on the truth of the condition, generates either expression A or an empty string.
* Title type usually not used, create through e_if().
*/
template<StrExpr A>
struct expr_if : expr_to_std_string<expr_if<A>> {
using symb_type = typename A::symb_type;
using my_type = expr_if<A>;
const A& a;
bool choice;
constexpr expr_if(const A& _a, bool _choice) : a(_a), choice(_choice){}
constexpr size_t length() const noexcept {
return choice ? a.length() : 0;
}
constexpr symb_type* place(symb_type* ptr) const noexcept {
return choice ? a.place(ptr) : ptr;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение условного выбора.
* @tparam A Тип ветки для true.
* @details Выражение, в зависимости от истинности условия генерирующее либо выражение A, либо строку из строкового литерала.
* Напрямую тип обычно не используется, создаётся через e_choice().
*
* Так как строковые литералы не являются строковыми выражениями, то использовать их в виде одиночного выражения в частях
* e_choice или e_if требовало бы их обрамления какими-либо конструкциями, преобразующими их в строковое выражение.
* Приходилось бы писать например так:
* ```cpp
* e_choice(condition, text, e_t("empty"));
* e_choice(condition, text, eea + "empty");
* e_choice(condition, text, "empty"_ss);
* e_if(!condition, "empty"_ss);
* ```
* Это, с одной стороны - захламляет код, с другой - делает его менее оптимальным.
* Поэтому для таких случаев сделаны перегрузки e_choice и e_if для случаев, когда их параметрами являются строковые литералы.
* В этих перегрузках и используются expr_choice_one_lit и expr_choice_two_lit, позволяя писать так:
* ```cpp
* e_choice(condition, text, "empty");
* e_choice(condition, "false", "true");
* e_if(!condition, "empty");
* ```
* @en @brief Conditional selection string expression.
* @tparam A Branch type for true.
* @details An expression that, depending on the truth of the condition, generates either expression A or a string from a string literal.
* The type is usually not used directly; it is created via e_choice().
*
* Since string literals are not string expressions, use them as a single expression in parts
* e_choice or e_if would require them to be surrounded by some constructs that convert them to a string expression.
* You would have to write something like this:
* ```cpp
* e_choice(condition, text, e_t("empty"));
* e_choice(condition, text, eea + "empty");
* e_choice(condition, text, "empty"_ss);
* e_if(!condition, "empty"_ss);
* ```
* This, on the one hand, clutters up the code, on the other, makes it less optimal.
* These overloads use expr_choice_one_lit and expr_choice_two_lit, allowing you to write like this:
* ```cpp
* e_choice(condition, text, "empty");
* e_choice(condition, "false", "true");
* e_if(!condition, "empty");
* ```
*/
template<typename L, StrExprForType<L> A, size_t N, bool Compare>
struct expr_choice_one_lit : expr_to_std_string<expr_choice_one_lit<L, A, N, Compare>> {
using symb_type = L;
const symb_type (&str)[N + 1];
const A& a;
bool choice;
constexpr expr_choice_one_lit(const symb_type (&_str)[N + 1], const A& _a, bool _choice) : str(_str), a(_a), choice(_choice){}
constexpr size_t length() const noexcept {
return choice == Compare ? a.length() : N;
}
constexpr symb_type* place(symb_type* ptr) const noexcept {
if (choice == Compare) {
return (L*)a.place((typename A::symb_type*)ptr);
}
if constexpr (N != 0) {
std::char_traits<symb_type>::copy(ptr, str, N);
}
return ptr + N;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение условного выбора.
* @details Выражение, в зависимости от истинности условия генерирующее либо один строковый литерал, либо другой.
* Напрямую тип обычно не используется, создаётся через e_choice().
*
* Так как строковые литералы не являются строковыми выражениями, то использовать их в виде одиночного выражения в частях
* e_choice или e_if требовало бы их обрамления какими-либо конструкциями, преобразующими их в строковое выражение.
* Приходилось бы писать например так:
* ```cpp
* e_choice(condition, text, e_t("empty"));
* e_choice(condition, text, eea + "empty");
* e_choice(condition, text, "empty"_ss);
* e_if(!condition, "empty"_ss);
* ```
* Это, с одной стороны - захламляет код, с другой - делает его менее оптимальным.
* Поэтому для таких случаев сделаны перегрузки e_choice и e_if для случаев, когда их параметрами являются строковые литералы.
* В этих перегрузках и используются expr_choice_one_lit и expr_choice_two_lit, позволяя писать так:
* ```cpp
* e_choice(condition, text, "empty");
* e_choice(condition, "false", "true");
* e_if(!condition, "empty");
* ```
* @en @brief Conditional selection string expression.
* @details An expression that, depending on the truth of the condition, generates either one string literal or another.
* The type is usually not used directly; it is created via e_choice().
*
* Since string literals are not string expressions, use them as a single expression in parts
* e_choice or e_if would require them to be surrounded by some constructs that convert them to a string expression.
* You would have to write something like this:
* ```cpp
* e_choice(condition, text, e_t("empty"));
* e_choice(condition, text, eea + "empty");
* e_choice(condition, text, "empty"_ss);
* e_if(!condition, "empty"_ss);
* ```
* This, on the one hand, clutters up the code, on the other, makes it less optimal.
* These overloads use expr_choice_one_lit and expr_choice_two_lit, allowing you to write like this:
* ```cpp
* e_choice(condition, text, "empty");
* e_choice(condition, "false", "true");
* e_if(!condition, "empty");
* ```
*/
template<typename K, size_t N, typename P, size_t M>
struct expr_choice_two_lit : expr_to_std_string<expr_choice_two_lit<K, N, P, M>> {
using symb_type = K;
const K (&str_a)[N + 1];
const P (&str_b)[M + 1];
bool choice;
constexpr expr_choice_two_lit(const K(&_str_a)[N + 1], const P(&_str_b)[M + 1], bool _choice)
: str_a(_str_a), str_b(_str_b), choice(_choice){}
constexpr size_t length() const noexcept {
return choice ? N : M;
}
constexpr symb_type* place(symb_type* ptr) const noexcept {
if (choice) {
if constexpr (N != 0) {
std::char_traits<symb_type>::copy(ptr, str_a, N);
}
return ptr + N;
}
if constexpr (M != 0) {
std::char_traits<symb_type>::copy(ptr, (const K(&)[M + 1])str_b, M);
}
return ptr + M;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Создание условного строкового выражения expr_choice.
* @tparam A - Тип выражение при истинности условия, выводится из аргумента.
* @tparam B - Тип выражения при ложности условия, выводится из аргумента.
* @param c - булево условие.
* @param a - строковое выражение, выполняющееся при `c == true`.
* @param b - строковое выражение, выполняющееся при `c == false`.
* @details Служит для возможности в одном выражении выбирать разные варианты в зависимости от условия.
* @en @brief Create a conditional string expression expr_choice.
* @tparam A - Type expression when the condition is true, inferred from the argument.
* @tparam B - The type of expression when the condition is false, inferred from the argument.
* @param c is a Boolean condition.
* @param a is a string expression that is executed when `c == true`.
* @param b is a string expression that is executed when `c == false`.
* @details Serves to allow you to select different options in one expression depending on the condition.
*
* @ru Примеры: @en Example: @~
* ```cpp
* columns_metadata.emplace_back(e_choice(name.is_empty(), "?column?", name) + "::" + metadata_column.type.to_string());
* ```
* ```cpp
* lstringa<512> str = e_choice(!ret_type_resolver_, sql_value::type_name(ret_type_), "any") + " " + name_ + "(";
* ```
* @ru Иначе такие операции приходилось бы разбивать на несколько модификаций строки или применению временных строк,
* что не оптимально и снизит производительность. (Это проверяется в бенчмарке "Build Full Func Name")
* @en Otherwise, such operations would have to be split into several string modifications or the use of temporary strings,
* which is not optimal and will reduce performance. (This is checked in the "Build Full Func Name" benchmark)
*/
template<StrExpr A, StrExprForType<typename A::symb_type> B>
constexpr expr_choice<A, B> e_choice(bool c, const A& a, const B& b) {
return {a, b, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Перегрузка e_choice, когда третий аргумент - строковый литерал.
* @en @brief Overload e_choice when the third argument is a string literal.
*/
template<StrExpr A, typename T, size_t N = const_lit_for<typename A::symb_type, T>::Count>
constexpr expr_choice_one_lit<typename const_lit<T>::symb_type, A, N - 1, true> e_choice(bool c, const A& a, T&& str) {
return {str, a, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Перегрузка e_choice, когда второй аргумент - строковый литерал.
* @en @brief Overload e_choice when the second argument is a string literal.
*/
template<StrExpr A, typename T, size_t N = const_lit_for<typename A::symb_type, T>::Count>
constexpr expr_choice_one_lit<typename const_lit<T>::symb_type, A, N - 1, false> e_choice(bool c, T&& str, const A& a) {
return {str, a, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Перегрузка e_choice, когда второй и третий аргумент - строковые литералы.
* @en @brief Overload e_choice when the second and third arguments are string literals.
*/
template<typename T, typename L, typename K = typename const_lit<T>::symb_type, typename P = typename const_lit<L>::symb_type,
size_t N = const_lit<T>::Count, size_t M = const_lit_for<typename const_lit<T>::symb_type, L>::Count>
requires is_equal_str_type_v<K, P>
constexpr expr_choice_two_lit<K, N -1, P, M - 1> e_choice(bool c, T&& str_a, L&& str_b) {
return {str_a, str_b, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Создание условного строкового выражения expr_if
* @tparam A - Тип выражение при истинности условия, выводится из аргумента
* @param c - булево условие
* @param a - строковое выражение, выполняющееся при `c == true`
* @details Служит для возможности в одном выражении генерировать в зависимости от условия либо указанный вариант, либо пустую строку.
* @en @brief Creating a conditional string expression expr_if
* @tparam A - Type expression when the condition is true, inferred from the argument
* @param c - boolean condition
* @param a - string expression executed when `c == true`
* @details Serves to allow one expression to generate, depending on the condition, either the specified option or an empty string.
*
* @ru Примеры: @en Example @~
* ```cpp
* void sql_func_info::build_full_name() {
* // Временный буфер для результата, возьмём с запасом
* // Temporary buffer for the result, take it with reserve
* lstringa<512> str = e_choice(!ret_type_resolver_, sql_value::type_name(ret_type_), "any") + " " + name_ + "(";
*
* bool add_comma = false;
*
* for (const auto& param : params_) {
* str += e_if(add_comma, ", ") + e_if(param.optional_, "[");
* // Добавляет к str названия допустимых типов
* // Adds the names of valid types to str
* param.allowed_types.to_string(str);
* if (param.optional_) {
* str += "]";
* }
* add_comma = true;
* }
* // Сохраним в stringa
* // Save it in stringa
* full_name_ = str + e_if(unlim_params_, e_if(add_comma, ", ") + "...") + ")";
* }
* ```
* @ru Иначе такие операции приходилось бы разбивать на несколько модификаций строки или применению временных строк,
* что не оптимально и снизит производительность. (Этот пример проверяется в бенчмарке "Build Full Func Name")
* @en Otherwise, such operations would have to be split into several string modifications or the use of temporary strings,
* which is not optimal and will reduce performance. (This example is tested in the "Build Full Func Name" benchmark)
*/
template<StrExpr A>
constexpr expr_if<A> e_if(bool c, const A& a) {
return {a, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Перегрузка e_if, когда второй аргумент - строковый литерал.
* @en @brief Overload e_if when the second argument is a string literal.
*/
template<typename T, size_t N = const_lit<T>::Count>
constexpr auto e_if(bool c, T&& str) {
using K = typename const_lit<T>::symb_type;
const K empty[1] = {0};
return expr_choice_two_lit<K, N - 1, K, 0>{str, empty, c};
}
/*!
* @ingroup StrExprs
* @ru @brief Тип для использования std::basic_string и std::basic_string_view как источников в строковых выражениях.
* @tparam K - тип символа.
* @tparam T - тип источника.
* @en @brief A type for using std::basic_string and std::basic_string_view as sources in string expressions.
* @tparam K is a symbol.
* @tparam T - source type.
*/
template<typename K, StdStrSourceForType<K> T>
struct expr_stdstr {
using symb_type = K;
const T& t_;
expr_stdstr(const T& t) : t_(t){}
constexpr size_t length() const noexcept {
return t_.length();
}
constexpr symb_type* place(symb_type* p) const noexcept {
size_t s = t_.size();
std::char_traits<K>::copy(p, (const K*)t_.data(), s);
return p + s;
}
};
/*!
* @ru @brief Специализация шаблона для преобразования стандартный строк в строковое выражение, позволяет использовать их в
* операциях конкатенации со строковыми выражениями.
* @tparam K - тип символов строкового выражения.
* @tparam T - тип числа.
* @en @brief A template specialization for converting standard strings to string expressions, allowing their use in
* concatenation operations with string expressions.
* @tparam K - the character type of the string expression.
* @tparam T - the number type.
*/
template<typename K, StdStrSource T>
struct convert_to_strexpr<K, T> {
using type = expr_stdstr<K, T>;
};
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<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;
template<typename K, bool I, typename T>
struct need_sign { // NOLINT
bool negate;
std::make_unsigned_t<T> val;
constexpr need_sign(T t) : negate(t < 0), val(t < 0 ? std::make_unsigned_t<T>{} - t : t) {}
constexpr void after(K*& ptr) {
if (negate)
*--ptr = '-';
}
};
template<typename K, typename T>
struct need_sign<K, false, T> {
T val;
constexpr need_sign(T t) : val(t){}
constexpr void after(K*&) {}
};
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> store(val);
K* itr = bufEnd;
while (store.val >= 100) {
const char* ptr = twoDigit + (store.val % 100) * 2;
*--itr = static_cast<K>(ptr[1]);
*--itr = static_cast<K>(ptr[0]);
store.val /= 100;
}
if (store.val < 10) {
*--itr = static_cast<K>('0' + store.val);
} else {
const char* ptr = twoDigit + store.val * 2;
*--itr = static_cast<K>(ptr[1]);
*--itr = static_cast<K>(ptr[0]);
}
store.after(itr);
return size_t(bufEnd - itr);
}
bufEnd[-1] = '0';
return 1;
}
template<typename K, typename T>
struct expr_num : expr_to_std_string<expr_num<K, T>> {
using symb_type = K;
using my_type = expr_num<K, T>;
enum { bufSize = 24 };
mutable K buf[bufSize];
mutable T value;
constexpr expr_num(T t) : value(t) {}
constexpr expr_num(expr_num&& t) noexcept : value(t.value) {}
constexpr size_t length() const noexcept {
value = (T)fromInt(buf + bufSize, value);
return (size_t)value;
}
constexpr K* place(K* ptr) const noexcept {
size_t len = (size_t)value;
ch_traits<K>::copy(ptr, buf + bufSize - len, len);
return ptr + len;
}
};
/*!
* @ru @brief Специализация шаблона для преобразования целых чисел в строковое выражение, позволяет использовать их в
* операциях конкатенации со строковыми выражениями.
* @tparam K - тип символов строкового выражения.
* @tparam T - тип числа.
* @en @brief A template specialization for converting integers to string expressions, allowing their use in
* concatenation operations with string expressions.
* @tparam K - the character type of the string expression.
* @tparam T - the number type.
*/
template<typename K, FromIntNumber T>
struct convert_to_strexpr<K, T> {
using type = expr_num<K, T>;
};
/*!
* @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, FromIntNumber T>
constexpr expr_num<K, T> e_num(T t) {
return {t};
}
namespace f{
enum class int_align { none, left, right, center };
enum class int_plus_sign {none, plus, space};
enum class int_prefix {none, lcase, ucase};
enum fmt_kinds : unsigned {
fmt_none = 0,
fmt_align = 1,
fmt_width = 2,
fmt_fill = 4,
fmt_sign = 8,
fmt_upper = 16,
fmt_prefix = 32,
fmt_zero = 64,
};
template<typename A, typename B, unsigned Kind>
struct fmt_info {
using a_t = A;
using b_t = B;
inline static constexpr unsigned kind = Kind;
};
struct fmt_default {
inline static constexpr unsigned kind = fmt_none;
};
template<int_align A>
struct align_info{
inline static constexpr unsigned kind = fmt_align;
};
template<size_t Width = 0>
struct width_info {
inline static constexpr unsigned kind = fmt_width;
};
template<unsigned Fill = ' '>
struct fill_info {
inline static constexpr unsigned kind = fmt_fill;
};
template<int_plus_sign S>
struct sign_info {
inline static constexpr unsigned kind = fmt_sign;
};
struct upper_info {
inline static constexpr unsigned kind = fmt_upper;
};
template<int_prefix>
struct prefix_info {
inline static constexpr unsigned kind = fmt_prefix;
};
struct zero_info {
inline static constexpr unsigned kind = fmt_zero;
};
template<typename T>
concept FmtParam = requires {
{std::remove_cvref_t<T>::kind} -> std::same_as<const unsigned&>;
};
template<FmtParam A, FmtParam B>
requires((A::kind & B::kind) == 0)
constexpr auto operator | (const A&, const B&) {
return fmt_info<A, B, A::kind | B::kind>{};
}
inline constexpr align_info<int_align::left> l;
inline constexpr align_info<int_align::right> r;
inline constexpr align_info<int_align::center> c;
template<size_t W>
inline constexpr width_info<W> w;
template<unsigned F>
inline constexpr fill_info<F> f;
inline constexpr sign_info<int_plus_sign::plus> sp;
inline constexpr sign_info<int_plus_sign::space> ss;
inline constexpr upper_info u;
inline constexpr prefix_info<int_prefix::lcase> p;
inline constexpr prefix_info<int_prefix::ucase> P;
inline constexpr zero_info z;
inline constexpr width_info<unsigned(-1)> wp;
inline constexpr fmt_default df;
template<typename T>
struct extract_align : std::false_type {
inline static constexpr int_align align = int_align::none;
};
template<int_align A>
struct extract_align<align_info<A>> : std::true_type {
inline static constexpr int_align align = A;
};
template<typename A, typename B, unsigned U>
struct extract_align<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_align<A>::value, in_b = extract_align<B>::value, value = in_a | in_b;
inline static constexpr int_align align = extract_align<std::conditional_t<in_a, A, std::conditional_t<in_b, B, align_info<int_align::none>>>>::align;
};
template<typename T>
struct extract_width : std::false_type {
inline static constexpr size_t width = 0;
};
template<size_t W>
struct extract_width<width_info<W>> : std::true_type {
inline static constexpr size_t width = W;
};
template<typename A, typename B, unsigned U>
struct extract_width<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_width<A>::value, in_b = extract_width<B>::value, value = in_a | in_b;
inline static constexpr size_t width = extract_width<std::conditional_t<in_a, A, std::conditional_t<in_b, B, width_info<0>>>>::width;
};
template<typename T>
struct extract_fill : std::false_type {
inline static constexpr unsigned fill = ' ';
};
template<unsigned F>
struct extract_fill<fill_info<F>> : std::true_type {
inline static constexpr unsigned fill = F;
};
template<typename A, typename B, unsigned U>
struct extract_fill<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_fill<A>::value, in_b = extract_fill<B>::value, value = in_a | in_b;
inline static constexpr unsigned fill = extract_fill<std::conditional_t<in_a, A, std::conditional_t<in_b, B, fill_info<' '>>>>::fill;
};
template<typename T>
struct extract_sign : std::false_type {
inline static constexpr int_plus_sign sign = int_plus_sign::none;
};
template<int_plus_sign S>
struct extract_sign<sign_info<S>> : std::true_type {
inline static constexpr int_plus_sign sign = S;
};
template<typename A, typename B, unsigned U>
struct extract_sign<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_sign<A>::value, in_b = extract_sign<B>::value, value = in_a | in_b;
inline static constexpr int_plus_sign sign = extract_sign<std::conditional_t<in_a, A, std::conditional_t<in_b, B, sign_info<int_plus_sign::none>>>>::sign;
};
template<typename T>
struct extract_upper : std::false_type {};
template<>
struct extract_upper<upper_info> : std::true_type {};
template<typename A, typename B, unsigned U>
struct extract_upper<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_upper<A>::value, in_b = extract_upper<B>::value,
value = extract_upper<std::conditional_t<in_a, A, std::conditional_t<in_b, B, std::false_type>>>::value;
};
template<typename T>
struct extract_prefix : std::false_type{
inline static constexpr int_prefix prefix = int_prefix::none;
};
template<int_prefix P>
struct extract_prefix<prefix_info<P>> : std::true_type {
inline static constexpr int_prefix prefix = P;
};
template<typename A, typename B, unsigned U>
struct extract_prefix<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_prefix<A>::value, in_b = extract_prefix<B>::value, value = in_a | in_b;
inline static constexpr int_prefix prefix = extract_prefix<std::conditional_t<in_a, A, std::conditional_t<in_b, B, prefix_info<int_prefix::none>>>>::prefix;
};
template<typename T>
struct extract_zero : std::false_type{};
template<>
struct extract_zero<zero_info> : std::true_type {};
template<typename A, typename B, unsigned U>
struct extract_zero<fmt_info<A, B, U>> {
inline static constexpr bool in_a = extract_zero<A>::value, in_b = extract_zero<B>::value,
value = extract_zero<std::conditional_t<in_a, A, std::conditional_t<in_b, B, std::false_type>>>::value;
};
template<int_align Align, unsigned Width, unsigned Fill, int_plus_sign Sign, bool Upper, int_prefix Prefix, bool Zero>
struct fmt_params {
inline static constexpr int_align align = Align;
inline static constexpr unsigned width = Width;
inline static constexpr unsigned fill = Fill;
inline static constexpr int_plus_sign sign = Sign;
inline static constexpr bool upper = Upper;
inline static constexpr int_prefix prefix = Prefix;
inline static constexpr bool zero = Zero;
};
template<FmtParam T>
using p_to_fmt_t = fmt_params<
extract_align<T>::align,
extract_width<T>::width,
extract_fill<T>::fill,
extract_sign<T>::sign,
extract_upper<T>::value,
extract_prefix<T>::prefix,
extract_zero<T>::value>;
template<FmtParam T>
using to_fmt_t = p_to_fmt_t<std::remove_cvref_t<T>>;
template<typename T>
struct is_fmt_params : std::false_type{};
template<int_align Align, unsigned Width, unsigned Fill, int_plus_sign Sign, bool Upper, int_prefix Prefix, bool Zero>
struct is_fmt_params<fmt_params<Align, Width, Fill, Sign, Upper, Prefix, Zero>> : std::true_type{};
template<typename T>
concept FmtParamSet = is_fmt_params<T>::value;
struct fmt_end{};
template<f::FmtParam F>
constexpr auto operator|(const F& f, fmt_end) {
return f;
}
template<char C = 0, char...Chars>
constexpr auto parse_fmt_symbol();
template<unsigned N, char C = 'Z', char...Chars>
constexpr auto parse_width() {
if constexpr (C >= '0' && C <= '9') {
return parse_width<N * 10 + C - '0', Chars...>();
} else {
return w<N> | parse_fmt_symbol<C, Chars...>();
}
}
template<unsigned N, char C = 'Z', char...Chars>
constexpr auto parse_fill() {
if constexpr (C >= '0' && C <= '9') {
return parse_fill<N * 16 + C - '0', Chars...>();
} else if constexpr (C >= 'a' && C <= 'f') {
return parse_fill<N * 16 + C - 'a' + 10, Chars...>();
} else if constexpr (C >= 'A' && C <= 'F') {
return parse_fill<N * 16 + C - 'A' + 10, Chars...>();
} else {
return f<N> | parse_fmt_symbol<C, Chars...>();
}
}
template<char C, char...Chars>
constexpr auto parse_fmt_symbol() {
if constexpr (C == '0') {
return z | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'a') {
return p | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'A') {
return P | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'b') {
return l | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'c') {
return c | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'd') {
return r | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'e') {
return sp | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'f') {
return ss | parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'E') {
return u | parse_fmt_symbol<Chars...>();
} else if constexpr (C == '\'') {
return parse_fmt_symbol<Chars...>();
} else if constexpr (C == 'F') {
return parse_fill<0, Chars...>();
} else if constexpr (C >= '1' && C <= '9') {
return parse_width<C - '0', Chars...>();
} else {
return fmt_end{};
}
}
template<unsigned R, typename T>
struct fmt_radix_info {};
template<unsigned N, char C = 0, char...Chars>
constexpr auto parse_radix() {
if constexpr (C >='0' && C <= '9') {
return parse_radix<N * 10 + C - '0', Chars...>();
} else if constexpr (C == 0) {
return fmt_radix_info<N, to_fmt_t<fmt_default>>{};
} else {
return fmt_radix_info<N, decltype(parse_fmt_symbol<C, Chars...>())>{};
}
};
template<char C1, char C2, char C3, char...Chars>
constexpr auto skip_0x() {
static_assert(C1 == '0' && C2 == 'x' && "Fmt symbols must begin with 0x");
static_assert(C3 >= '1' && C3 <= '9' && "Radix must begin with 1-9");
return parse_radix<C3 - '0', Chars...>();
}
} // namespace f
template<typename K, bool Ucase>
inline constexpr K digits_symbols[36] = {K('0'), K('1'), K('2'), K('3'), K('4'), K('5'), K('6'), K('7'), K('8'), K('9'),
K(Ucase ? 'A' : 'a'), K(Ucase ? 'B' : 'b'), K(Ucase ? 'C' : 'c'), K(Ucase ? 'D' : 'd'), K(Ucase ? 'E' : 'e'),
K(Ucase ? 'F' : 'f'), K(Ucase ? 'G' : 'g'), K(Ucase ? 'H' : 'h'), K(Ucase ? 'I' : 'i'), K(Ucase ? 'J' : 'j'),
K(Ucase ? 'K' : 'k'), K(Ucase ? 'L' : 'l'), K(Ucase ? 'M' : 'm'), K(Ucase ? 'N' : 'n'), K(Ucase ? 'O' : 'o'),
K(Ucase ? 'P' : 'p'), K(Ucase ? 'Q' : 'q'), K(Ucase ? 'R' : 'r'), K(Ucase ? 'S' : 's'), K(Ucase ? 'T' : 't'),
K(Ucase ? 'U' : 'u'), K(Ucase ? 'V' : 'v'), K(Ucase ? 'W' : 'w'), K(Ucase ? 'X' : 'x'), K(Ucase ? 'Y' : 'y'),
K(Ucase ? 'Z' : 'z'),
};
template<FromIntNumber T, unsigned Radix, f::FmtParamSet FP> requires (Radix > 1 && Radix <= 36)
struct expr_integer_src {
T value_;
unsigned width_{};
constexpr expr_integer_src(T v) : value_(v){}
constexpr expr_integer_src(T v, unsigned w) : value_(v), width_(w){}
template<is_std_string_v S>
operator S() const;
template<typename S> requires std::is_constructible_v<S, empty_expr<typename S::symb_type>>
operator S() const;
};
template<is_one_of_char_v K, FromIntNumber T, unsigned Radix, f::FmtParamSet FP>
requires (Radix > 1 && Radix <= 36)
struct expr_integer : expr_to_std_string<expr_integer<K, T, Radix, FP>> {
using symb_type = K;
using my_type = expr_num<K, T>;
enum { bufSize = 64 };
mutable K buf[bufSize];
mutable T value_;
unsigned width_{};
mutable bool negate_{};
constexpr expr_integer(T t) : value_(t){}
constexpr expr_integer(T t, unsigned w) : value_(t), width_(w){}
constexpr expr_integer(const expr_integer_src<T, Radix, FP>& v) : value_(v.value_), width_(v.width_){}
constexpr expr_integer(expr_integer&& t) noexcept : value_(t.value_), width_(t.width_){}
constexpr size_t length() const noexcept {
K* bufEnd = std::end(buf), *itr = bufEnd;
if (value_) {
need_sign<K, std::is_signed_v<T>, T> store(value_);
while (store.val) {
*--itr = digits_symbols<K, FP::upper>[store.val % Radix];
store.val /= Radix;
}
if constexpr (std::is_signed_v<T>) {
negate_ = store.negate;
}
} else {
*--itr = digits_symbols<K, FP::upper>[0];
}
size_t len = bufEnd - itr;
value_ = T(len);
if constexpr (std::is_signed_v<T>) {
if constexpr (FP::sign != f::int_plus_sign::none) {
len++;
} else {
if (negate_) {
len++;
}
}
}
if constexpr (FP::prefix != f::int_prefix::none && (Radix == 2 || Radix == 8 || Radix == 16)) {
len++; // add 0
if constexpr (Radix != 8) { // for octo just add 0,
len++; // for other b or x
}
}
if constexpr (FP::width == unsigned(-1)) {
return std::max<size_t>(width_, len);
} else {
return std::max<size_t>(FP::width, len);
}
}
constexpr K* place(K* ptr) const noexcept {
size_t len = (size_t)value_, all_len = len;
if constexpr (std::is_signed_v<T>) {
if constexpr (FP::sign != f::int_plus_sign::none) {
all_len++;
} else {
if (negate_) {
all_len++;
}
}
}
if constexpr (FP::prefix != f::int_prefix::none && (Radix == 2 || Radix == 8 || Radix == 16)) {
all_len++; // add 0
if constexpr (Radix != 8) { // for octo just add 0,
all_len++; // for other b or x
}
}
if constexpr (FP::zero) {
if constexpr (std::is_signed_v<T>) {
if (negate_) {
*ptr++ = K('-');
} else {
if constexpr (FP::sign == f::int_plus_sign::plus) {
*ptr++ = K('+');
} else if constexpr (FP::sign == f::int_plus_sign::space) {
*ptr++ = K(' ');
}
}
}
if constexpr (FP::prefix != f::int_prefix::none && (Radix == 2 || Radix == 8 || Radix == 16)) {
*ptr++ = K('0');
if constexpr (Radix == 2) {
*ptr++ = FP::prefix == f::int_prefix::lcase ? K('b') : K('B');
} else if constexpr (Radix == 16) {
*ptr++ = FP::prefix == f::int_prefix::lcase ? K('x') : K('X');
}
}
size_t before = 0;
if constexpr (FP::width == unsigned(-1)) {
if (width_ > all_len) {
before = width_ - all_len;
}
} else {
if (FP::width > all_len) {
before = FP::width - all_len;
}
}
if (before) {
ch_traits<K>::assign(ptr, before, K('0'));
ptr += before;
}
ch_traits<K>::copy(ptr, std::end(buf) - len, len);
ptr += len;
} else {
size_t before = 0, after = 0;
if constexpr (FP::width == unsigned(-1)) {
if (width_ > all_len) {
if constexpr (FP::align == f::int_align::left) {
after = width_ - all_len;
} else if constexpr (FP::align == f::int_align::center) {
before = (width_ - all_len) / 2;
after = width_ - all_len - before;
} else {
before = width_ - all_len;
}
}
} else {
if (FP::width > all_len) {
if constexpr (FP::align == f::int_align::left) {
after = FP::width - all_len;
} else if constexpr (FP::align == f::int_align::center) {
before = (FP::width - all_len) / 2;
after = FP::width - all_len - before;
} else {
before = FP::width - all_len;
}
}
}
if (before) {
ch_traits<K>::assign(ptr, before, K(FP::fill));
ptr += before;
}
if constexpr (std::is_signed_v<T>) {
if (negate_) {
*ptr++ = K('-');
} else {
if constexpr (FP::sign == f::int_plus_sign::plus) {
*ptr++ = K('+');
} else if constexpr (FP::sign == f::int_plus_sign::space) {
*ptr++ = K(' ');
}
}
}
if constexpr (FP::prefix != f::int_prefix::none && (Radix == 2 || Radix == 8 || Radix == 16)) {
*ptr++ = K('0');
if constexpr (Radix == 2) {
*ptr++ = FP::prefix == f::int_prefix::lcase ? K('b') : K('B');
} else if constexpr (Radix == 16) {
*ptr++ = FP::prefix == f::int_prefix::lcase ? K('x') : K('X');
}
}
ch_traits<K>::copy(ptr, std::end(buf) - len, len);
ptr += len;
if (after) {
ch_traits<K>::assign(ptr, after, K(FP::fill));
ptr += after;
}
}
return ptr;
}
};
template<FromIntNumber T, unsigned Radix, f::FmtParamSet FP> requires (Radix > 1 && Radix <= 36)
template<is_std_string_v S>
expr_integer_src<T, Radix, FP>::operator S() const {
using st = typename S::value_type;
using Al = typename S::allocator_type;
return to_std_string<st, Al>(expr_integer<st, T, Radix, FP>{value_, width_});
}
template<FromIntNumber T, unsigned Radix, f::FmtParamSet FP> requires (Radix > 1 && Radix <= 36)
template<typename S> requires std::is_constructible_v<S, empty_expr<typename S::symb_type>>
expr_integer_src<T, Radix, FP>::operator S() const {
using st = typename S::symb_type;
return S{expr_integer<st, T, Radix, FP>{value_, width_}};
}
template<typename K, FromIntNumber T, unsigned Radix, f::FmtParamSet FP>
struct convert_to_strexpr<K, expr_integer_src<T, Radix, FP>> {
using type = expr_integer<K, T, Radix, FP>;
};
template<unsigned R, typename T, typename F>
struct flags_checker {
using val_t = T;
using flags_t = f::to_fmt_t<F>;
inline static constexpr unsigned Radix = R;
};
template<typename T, bool ArgWidth>
concept good_int_flags =
T::Radix > 1 && T::Radix <= 36
// Должно быть задано только или выравнивание, или заполнение нулём.
// Only either alignment or zero-padding must be specified.
&& (T::flags_t::align == f::int_align::none || !T::flags_t::zero)
// Флаг вывода знака должен задаваться только для знаковых чисел
// The sign output flag should only be set for signed numbers.
&& (std::is_signed_v<typename T::val_t> || T::flags_t::sign == f::int_plus_sign::none)
// Неверное поле ширины
// Invalid width field
&& (T::flags_t::width == unsigned(-1)) == ArgWidth
// Префикс может быть только при основании 2, 8, 16
// Prefix may by only for radix 2, 8, 16
&& (T::flags_t::prefix == f::int_prefix::none || T::Radix == 2 || T::Radix == 8 || T::Radix == 16);
/*!
* @ingroup StrExprs
* @ru @brief Создает объект, который преобразовывается в строковое выражение, генерирующее строковое представление числа.
* @tparam R - Основание счисления, должно быть от 2 до 36.
* @tparam fp - параметры для форматирования числа.
* @tparam T - тип числа, выводится из аргумента.
* @param v - число.
* @return источник для строкового выражения expr_integer_src.
* @details параметры форматирования числа задаются набором констант, через '|'.
* - f::l при задании ширины поля выравнивать влево.
* - f::r при задании ширины поля выравнивать вправо.
* - f::с при задании ширины поля выравнивать по центру.
* - f::p выводить префикс системы счисления, 0b для 2, 0 для 8, 0x для 16.
* - f::P выводить префикс системы счисления, 0B для 2, 0 для 8, 0X для 16.
* - f::z дополнять число до заданной ширины нулями слева. Не совместимо с опциями выравнивания.
* - f::u Для систем счисления более 10, выводить символы в верхнем регистре.
* - f::sp Для знаковых типов чисел для положительных значений выводить '+' перед числом.
* - f::ss Для знаковых типов чисел для положительных значений выводить пробел перед числом.
* - f::f<'c'>` Задаёт символ-заполнитель при указании ширины поля. По умолчанию - пробел.
* - f::w<N> Задаёт ширину поля. При указании ширины можно либо указать желаемое выравнивание (r, l, c),
* либо дополнение нулями (z), но не оба сразу. Если ничего из этого не указано, применяется выравнивание вправо.
* Число дополняется до заданной ширины, если оно короче. Если длиннее, то выводится всё число.
* - f::wp То же самое, что и f::w<-1>. В этом случае ширина должна передаваться дополнительным аргументом e_int.
* <br/>
* @en @brief Creates an object that is converted to a string expression that generates a string representation of a number.
* @tparam R - Number base, must be from 2 to 36.
* @tparam fp - parameters for format number.
* @tparam T - number type, deduced from the argument.
* @param v - number.
* @return the source for the string expression expr_integer_src.
* @details number formatting parameters are specified by a set of constants, via '|'.
* - f::l when setting the field width, align to the left.
* - f::r when setting the field width, align to the right.
* - f::с when setting the field width, align to the center.
* - f::p print number system prefix, 0b for 2, 0 for 8, 0x for 16.
* - f::P print number system prefix, 0B for 2, 0 for 8, 0X for 16.
* - f::z pad the number to the specified width with zeros on the let. Not compatible with alignment options.
* - f::u For radix greater than 10, output characters in uppercase.
* - f::sp For signed number types, print '+' before the number for positive values.
* - f::ss For signed number types, print a space before the number for positive values.
* - f::f<'c'> Specifies a placeholder character when specifying the field width. Default is space.
* - f::w<N> Sets the field width. When specifying the width, you can either specify the desired alignment (r, l, c),
* or zero padding (z), but not both. If none of these are specified, right alignment is applied.
* The number is padded to the given width if it is shorter. If it is longer, then the entire number is displayed.
* - f::wp Same as f::w<-1>. In this case, the width must be passed as an additional argument e_int.
* <br/>
* @ru Пример: @en Example: @~
* ```cpp
* stringu u16t = u"Number "_ss + num
* + " in octal is " + e_int<8, f::w<16> | f::z>(num)
* + ", in binary is " + e_int<2, f::w<32> | f::p | f::z>(num);
* ```
* @ru Возможна также сокращённая запись этой функции в виде `num / 0xПараметрыФорматирования_fmt`.
* Параметры форматирования задаются следующим образом: сначала идёт `0x`, затем основание счисления, записанное в
* десятичном виде. Далее могут идти символы, обозначающие различные флаги:
* - b при задании ширины поля выравнивать влево, аналог f::l.
* - d при задании ширины поля выравнивать вправо, аналог f::r.
* - с при задании ширины поля выравнивать по центру, аналог f::c.
* - a выводить префикс системы счисления, 0b для 2, 0 для 8, 0x для 16, аналог f::p.
* - A выводить префикс системы счисления, 0B для 2, 0 для 8, 0X для 16, аналог f::P.
* - 0 дополнять число до заданной ширины нулями слева. Не совместимо с опциями выравнивания, аналог f::z.
* - E Для систем счисления более 10, выводить символы в верхнем регистре, аналог f::u.
* - e Для знаковых типов чисел для положительных значений выводить '+' перед числом, аналог f::sp.
* - f Для знаковых типов чисел для положительных значений выводить пробел перед числом, аналог f::ss.
* - FКодCимволаВhex Задаёт код символа-заполнителя при указании ширины поля, аналог f::f<'c'>.
* - Число в десятичном виде, начинающееся не с 0. Задаёт ширину поля, аналог f::w<N>.
* При указании ширины можно либо указать желаемое выравнивание (b, c, d),
* либо дополнение нулями (0), но не оба сразу. Если ничего из этого не указано, применяется выравнивание вправо.
* Число дополняется до заданной ширины, если оно короче. Если длиннее, то выводится всё число.
* - ' разделитель, пропускается.
*
* Так как после `F` все символы воспринимаются как hex код символа разделителя, при необходимости его использования
* лучше ставить его в конце литерала форматирования, или отделять '.
* <br/>
* @en It is also possible to write this function in a shortened form: `num / 0xFormatOptions_fmt`.
* Formatting parameters are set as follows: first comes `0x`, then the radix written in
* decimal form. Next may be symbols indicating various flags:
* - b when setting the field width, align to the left, analogous to f::l.
* - d when setting the field width, align to the right, analogous to f::r.
* - c when setting the field width, align to the center, analogous to f::c.
* - a display the number system prefix, 0b for 2, 0 for 8, 0x for 16, analogous to f::p.
* - A display the number system prefix, 0B for 2, 0 for 8, 0X for 16, analogous to f::P.
* - 0 pad the number to the specified width with zeros on the left. Not compatible with alignment options, similar to f::z.
* - E For radix greater than 10, display characters in uppercase, analogous to f::u.
* - e For signed number types, for positive values, print '+' before the number, analogous to f::sp.
* - f For signed number types, for positive values, print a space before the number, analogous to f::ss.
* - FCodeInhex Sets the code of the filler character when specifying the field width, analogous to f::f<'c'>.
* - Number in decimal form, not starting from 0. Sets the field width, analogous to f::w<N>.
* When specifying the width, you can either specify the desired alignment (b, c, d),
* or zero padding (0), but not both. If none of these are specified, right alignment is applied.
* The number is padded to the given width if it is shorter. If it is longer, then the entire number is displayed.
* - ' delimiter, skipped.
*
* Since after `F` all characters are perceived as hex code of the delimiter character, if necessary, use it
* it is better to put it at the end of format literal, or separate with '.
* <br/>
* @ru Пример: @en Example: @~
* ```cpp
* stringu u16t = u"Number "_ss + num
* + " in octal is " + num / 0x8'016_fmt // same as e_int<8, f::w<16> | f::z>(num)
* + ", in binary is " + num / 0x2a032_fmt; // same as e_int<2, f::w<32> | f::p | f::z>(num);
* ....
* stringa text = "Count is "_ss + count / 0x16A08E_fmt; // same as e_int<16, f::P | f::z | f::w<8> | f::u>(count)
* ....
* stringa text = "Number "_ss + count / 0x16c20EF5F_fmt; // same as e_int<16, f::c | f::w<20> | f::u | f::f<'_'>>(count)
* ```
*/
template<unsigned R, auto fp = f::df, FromIntNumber T> requires good_int_flags<flags_checker<R, T, decltype(fp)>, false>
constexpr auto e_int(T v) {
using fmt_param = f::to_fmt_t<decltype(fp)>;
return expr_integer_src<T, R, fmt_param>{v};
}
/*!
* @ru @brief То же самое, что `e_int(num)`, только дополнительно передаётся ширина поля.
* @en @brief Same as `e_int(num)`, only the field width is additionally specified.
*/
template<unsigned R, auto fp = f::df, FromIntNumber T> requires good_int_flags<flags_checker<R, T, decltype(fp)>, true>
constexpr auto e_int(T v, unsigned w) {
using fmt_param = f::to_fmt_t<decltype(fp)>;
return expr_integer_src<T, R, fmt_param>{v, w};
}
template<typename T, unsigned R, typename F> requires good_int_flags<flags_checker<R, T, F>, false>
auto operator / (T v, const f::fmt_radix_info<R, F>&) {
return expr_integer_src<T, R, f::to_fmt_t<F>>{v};
}
template<typename K>
struct expr_real : expr_to_std_string<expr_real<K>> {
using symb_type = K;
mutable u8s buf[40];
mutable size_t l;
double v;
constexpr expr_real(double d) : v(d) {}
constexpr expr_real(float d) : v(d) {}
size_t length() const noexcept {
auto [ptr, ec] = std::to_chars(buf, buf + std::size(buf), v);
l = ec != std::errc{} ? 0 : ptr - buf;
return l;
}
K* place(K* ptr) const noexcept {
if constexpr (sizeof(K) == sizeof(buf[0])) {
ch_traits<K>::copy(ptr, (K*)buf, l);
} else {
for (size_t i = 0; i < l; i++) {
ptr[i] = buf[i];
}
}
return ptr + l;
}
};
/*!
* @ru @brief Специализация шаблона для преобразования целых чисел в строковое выражение, позволяет использовать их в
* операциях конкатенации со строковыми выражениями.
* @tparam K - тип символов строкового выражения.
* @tparam T - тип числа.
* @en @brief A template specialization for converting integers to string expressions, allowing their use in
* concatenation operations with string expressions.
* @tparam K - the character type of the string expression.
* @tparam T - the number type.
*/
template<typename K, std::floating_point T>
struct convert_to_strexpr<K, T> {
using type = expr_real<K>;
};
/*!
* @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_char_v<K>
inline constexpr expr_real<K> e_num(double t) {
return {t};
}
template<FromIntNumber Val, bool All, bool Ucase, bool Ox>
struct expr_hex_src {
explicit constexpr expr_hex_src(Val v) : v_(v){}
Val v_;
};
template<typename K, FromIntNumber Val, bool All, bool Ucase, bool Ox>
struct expr_hex : expr_to_std_string<expr_hex<K, Val, All, Ucase, Ox>> {
using symb_type = K;
mutable need_sign<K, std::is_signed_v<Val>, Val> v_;
mutable K buf_[sizeof(Val) * 2]{};
explicit constexpr expr_hex(Val v) : v_(v){}
constexpr expr_hex(const expr_hex_src<Val, All, Ucase, Ox>& v) : v_(v.v_){}
constexpr size_t length() const noexcept {
K *ptr = buf_ + std::size(buf_);
size_t l = 0;
for (;;) {
*--ptr = digits_symbols<K, Ucase>[v_.val & 0xF];
v_.val >>= 4;
l++;
if (v_.val) {
*--ptr = digits_symbols<K, Ucase>[v_.val & 0xF];
v_.val >>= 4;
l++;
}
if (!v_.val) {
if constexpr (All) {
if (size_t need = sizeof(Val) * 2 - l) {
ch_traits<K>::assign(buf_, need, K('0'));
}
l = sizeof(Val) * 2;
}
break;
}
}
v_.val = l;
if constexpr (std::is_signed_v<Val>) {
return l + (Ox ? 2 : 0) + (v_.negate ? 1 : 0);
}
return l + (Ox ? 2 : 0);
}
constexpr K* place(K* ptr) const noexcept {
if constexpr (std::is_signed_v<Val>) {
if (v_.negate) {
*ptr++ = K('-');
}
}
if constexpr (Ox) {
*ptr++ = K('0');
*ptr++ = K('x');
}
if constexpr (All) {
ch_traits<K>::copy(ptr, buf_, sizeof(Val) * 2);
return ptr + sizeof(Val) * 2;
} else {
ch_traits<K>::copy(ptr, buf_ + std::size(buf_) - v_.val, v_.val);
return ptr + v_.val;
}
}
};
/*!
* @ingroup StrExprs
* @ru @brief Флаги для функции e_hex.
* @en @brief Flags for the e_hex function.
*/
enum HexFlags : unsigned {
Short = 1, ///< without leading zeroes
No0x = 2, //< without 0x prefix
Lcase = 4, //< Use lower case
};
/*!
* @ingroup StrExprs
* @ru @brief Создает объект, который может преобразовываться в строковое выражение, генерирующее 16ричное представление числа.
* @tparam Flags - флаги форматирования, побитовое ИЛИ из HexFlags.
* @tparam T - тип числа, выводится автоматически.
* @param v - число.
* @details Это более быстрое преобразование в строку в 16ричном виде, чем через `e_int`, однако с меньшими возможностями
* форматирования. По умолчанию создается представление в виде `0x000012AB`, то есть префикс `0x` и дополнения нулями до ширины
* по размеру типа числа (2 символа на байт). Можно указать флаги:
* - HexFlags::Short - не дополнять нулями до фиксированной ширины.
* - HexFlags::Lcase - выводить символы в нижнем регистре.
* - HexFlags::No0x - не выводить префикс.
*
* Кроме того, возможна краткая запись этого выражения в виде `num / N_f16`
* N обозначает флаги форматирования
* - 1 - HexFlags::Short
* - 2 - HexFlags::Lcase
* - 3 - HexFlags::No0x
*
* @en @brief Creates an object that can be converted to a string expression that generates a hexadecimal representation of a number.
* @tparam Flags - format flags, bitwise OR of HexFlags.
* @tparam T - number type, deducted automatically.
* @param v - number.
* @details This is a faster conversion to a hexadecimal string than `e_int`, but with less power formatting.
* By default, a representation of the form `0x000012AB` is created, that is, prefixed with `0x` and padded with zeros to the width
* by number type size (2 characters per byte). You can specify flags:
* - HexFlags::Short - do not pad with zeros to a fixed width.
* - HexFlags::Lcase - display characters in lowercase.
* - HexFlags::No0x - do not display the prefix.
*
* In addition, this expression can be written briefly as `num / N_f16`
* N stands for formatting flags
* - 1 - HexFlags::Short
* - 2 - HexFlags::Lcase
* - 3 - HexFlags::No0x
*
* @ru Пример @en Example @~
* ```cpp
* stringa text = +"val = "sv + e_hex(10);
* EXPECT_EQ(text, "val = 0x0000000A");
*
* stringu textu = +u"val = 0X"sv + e_hex<HexFlags::No0x | HexFlags::Short | HexFlags::Lcase>(0x12A);
* EXPECT_EQ(textu, u"val = 0X12a");
*
* text = "Num in hex: " + num / 0_f16; // same as e_hex(num);
* text = "Num in hex: " + num / 13_f16; // same as e_hex<HexFlags::Short | HexFlags::No0x>(num);
* text = "Num in hex: " + num / 123_f16; // same as e_hex<HexFlags::Short | HexFlags::No0x | HexFlags::Lcase>(num);
* text = "Num in hex: " + num / 2_f16; // same as e_hex<HexFlags::No0x | HexFlags::Lcase>(num);
* ```
*/
template<unsigned Flags = 0, FromIntNumber T>
constexpr auto e_hex(T v) {
return expr_hex_src<T, (Flags & HexFlags::Short) == 0, (Flags & HexFlags::Lcase) == 0, (Flags & HexFlags::No0x) == 0>{v};
}
template<char c = 0, char...Chars>
constexpr unsigned parse_f16_flags() {
if constexpr (c == '1') {
return HexFlags::Short | parse_f16_flags<Chars...>();
} else if constexpr (c == '2') {
return HexFlags::Lcase | parse_f16_flags<Chars...>();
} else if constexpr (c == '3') {
return HexFlags::No0x | parse_f16_flags<Chars...>();
} else {
return 0;
}
}
template<unsigned N>
struct f16flags{};
template<FromIntNumber T, unsigned Flags>
constexpr auto operator/(T v, const f16flags<Flags>&) {
return expr_hex_src<T, (Flags & HexFlags::Short) == 0, (Flags & HexFlags::Lcase) == 0, (Flags & HexFlags::No0x) == 0>{v};
}
inline namespace literals {
template<char...Chars>
constexpr auto operator""_f16() {
return f16flags<parse_f16_flags<Chars...>()>{};
}
} // namespace literals
/*!
* @ru @brief Специализация шаблона для преобразования e_hex в строковое выражение, позволяет использовать их в
* операциях конкатенации со строковыми выражениями.
* @tparam K - тип символов строкового выражения.
* @en @brief A template specialization for converting e_hex to string expressions, allowing their use in
* concatenation operations with string expressions.
* @tparam K - the character type of the string expression.
*/
template<typename K, typename Val, bool All, bool Ucase, bool Ox>
struct convert_to_strexpr<K, expr_hex_src<Val, All, Ucase, Ox>> {
using type = expr_hex<K, Val, All, Ucase, Ox>;
};
template<typename K>
struct expr_pointer : expr_hex<K, uintptr_t, true, true, true> {
constexpr expr_pointer(const void* ptr) : expr_hex<K, uintptr_t, true, true, true>((uintptr_t)ptr){}
};
/*!
* @ru @brief Специализация шаблона для преобразования указателей в строковое выражение, позволяет использовать их в
* операциях конкатенации со строковыми выражениями.
* @tparam K - тип символов строкового выражения.
* @en @brief A template specialization for converting pointers to string expressions, allowing their use in
* concatenation operations with string expressions.
* @tparam K - the character type of the string expression.
*/
template<typename K, typename T>
struct convert_to_strexpr<K, const T*> {
using type = expr_pointer<K>;
};
template<typename K, StrExprForType<K> A, bool Left>
struct expr_fill : expr_to_std_string<expr_fill<K, A, Left>>{
using symb_type = K;
K symbol_;
size_t width_;
const A& a_;
mutable size_t alen_{};
constexpr expr_fill(K symbol, size_t width, const A& a) : symbol_(symbol), width_(width), a_(a){}
constexpr size_t length() const noexcept {
alen_ = a_.length();
return std::max(alen_, width_);
}
constexpr K* place(K* ptr) const noexcept {
if (alen_ >= width_) {
return (K*)a_.place((typename A::symb_type*)ptr);
}
size_t w = width_ - alen_;
if constexpr (Left) {
ch_traits<K>::assign(ptr, w, symbol_);
ptr += w;
return (K*)a_.place((typename A::symb_type*)ptr);
} else {
ptr = (K*)a_.place((typename A::symb_type*)ptr);
ch_traits<K>::assign(ptr, w, symbol_);
return ptr + w;
}
}
};
/*!
* @ingroup StrExprs
* @ru @brief Создает выражение, которое дополняет указанное строковое выражение до заданной длины
* заданным символом слева.
* @param width - дополнять до этой ширины
* @param symbol - символ для заполнения
* @details Если строковое выражение выдаёт строку короче заданной длины, добавляет перед ней
* указанный символ, дополняя до нужной длины. Не обрезает строку до указанной длины.
* Будьте внимательны, длина берётся в code units, не в code points, а символ заполнения
* не может быть суррогатным, то есть занимать более одного code unit. Если надо быть точным
* с Unicode-символами - используйте конвертацию в char32_t и обратно.
* @en @brief Creates an expression that expands the specified string expression to the specified length
* given character to the left.
* @param width - pad to this width
* @param symbol - symbol to fill
* @details If a string expression produces a string shorter than the given length, prepend it with
* the specified character, padding to the desired length. Does not truncate the string to the specified length.
* Be careful, the length is taken in code units, not in code points, and the padding character
* cannot be a surrogate, that is, occupy more than one code unit. If you have to be precise
* with Unicode characters - use conversion to char32_t and vice versa.
*/
template<StrExpr A, typename K = typename A::symb_type>
expr_fill<K, A, true> e_fill_left(const A& a, size_t width, K symbol = K(' ')) {
return {symbol, width, a};
}
/*!
* @ingroup StrExprs
* @ru @brief Создает выражение, которое дополняет указанное строковое выражение до заданной длины
* заданным символом справа.
* @param width - дополнять до этой ширины
* @param symbol - символ для заполнения
* @details Если строковое выражение выдаёт строку короче заданной длины, добавляет к ней
* указанный символ, дополняя до нужной длины. Не обрезает строку до указанной длины.
* Будьте внимательны, длина берётся в code units, не в code points, а символ заполнения
* не может быть суррогатным, то есть занимать более одного code unit. Если надо быть точным
* с Unicode-символами - используйте конвертацию в char32_t и обратно.
* @en @brief Creates an expression that expands the specified string expression to the specified length
* given character to the right.
* @param width - pad to this width
* @param symbol - symbol to fill
* @details If a string expression produces a string shorter than the given length, appends it
* the specified character, padding to the desired length. Does not truncate the string to the specified length.
* Be careful, the length is taken in code units, not in code points, and the padding character
* cannot be a surrogate, that is, occupy more than one code unit. If you have to be precise
* with Unicode characters - use conversion to char32_t and vice versa.
*/
template<StrExpr A, typename K = typename A::symb_type>
expr_fill<K, A, false> e_fill_right(const A& a, size_t width, K symbol = K(' ')) {
return {symbol, width, a};
}
/*
* Для создания строковых конкатенаций с векторами и списками, сджойненными константным разделителем
* 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 : expr_to_std_string<expr_join<K, T, I, tail, skip_empty>> {
using symb_type = K;
using my_type = expr_join<K, T, I, tail, skip_empty>;
const T& s;
const K* delim;
constexpr expr_join(const T& _s, const K* _delim) : s(_s), delim(_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.data(), 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<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<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;
};
/*!
* @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>();
template<typename T>
struct convert_result {
T value;
IntConvertResult ec;
size_t read;
};
struct int_convert { // NOLINT
inline static constexpr 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 constexpr std::make_unsigned_t<K> 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] : us;
}
}
}
template<typename K, ToIntNumber T, unsigned Base, bool CheckOverflow>
requires(Base != 0)
static constexpr convert_result<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 u_type 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 u_type 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 u_type 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, size_t(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 constexpr convert_result<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, size_t(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, size_t(ptr - start)};
}
};
template<typename K, typename Impl>
class null_terminated {
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
*/
constexpr const K* c_str() const { return static_cast<const Impl*>(this)->symbols(); }
};
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> {
constexpr 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 buffer of string characters.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr const K* end() const { return d().symbols() + d().length(); }
/*!
* @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.
*/
constexpr const K* cbegin() 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.
*/
constexpr const K* cend() const { return d().symbols() + d().length(); }
};
template<typename K, typename Impl>
class buffer_pointers<K, Impl, true> : public buffer_pointers<K, Impl, false> {
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr const K* end() const { return base::end(); }
/*!
* @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.
*/
constexpr const K* cbegin() const { return base::cbegin(); }
/*!
* @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.
*/
constexpr const K* cend() const { return base::cend(); }
/*!
* @ru @brief Получить указатель на буфер символов строки.
* @return K* - указатель на буфер символов строки.
* @en @brief Get a pointer to the string's character buffer.
* @return K* - pointer to a string character buffer.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr K* end() { return d().str() + d().length(); }
};
/*!
* @ru @brief Класс для последовательного получения подстрок по заданному разделителю.
* @tparam K - тип символов.
* @en @brief Class for sequentially obtaining substrings by a given delimiter.
* @tparam K - character type.
*/
template<typename K, typename StrSrc>
class SplitterBase {
using str_t = StrSrc;
str_t text_;
str_t delim_;
public:
constexpr SplitterBase(str_t text, str_t delim) : text_(text), delim_(delim) {}
/*!
* @ru @brief Узнать, не закончились ли подстроки.
* @en @brief Find out if substrings are running out.
*/
constexpr bool is_done() const {
return text_.length() == str::npos;
}
/*!
* @ru @brief Получить следующую подстроку.
* @return simple_str.
* @en @brief Get the next substring.
* @return simple_str.
*/
constexpr str_t 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();
}
str_t result{text_.str, pos};
text_.str += next;
text_.len -= next;
return result;
}
};
/*!
* @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.
* Doesnt 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_src_algs : public buffer_pointers<K, Impl, Mutable> {
constexpr const Impl& d() const noexcept {
return *static_cast<const Impl*>(this);
}
constexpr size_t _len() const noexcept {
return d().length();
}
constexpr const K* _str() const noexcept {
return d().symbols();
}
constexpr bool _is_empty() const noexcept {
return d().is_empty();
}
public:
using symb_type = K;
using str_piece = StrRef;
using traits = ch_traits<K>;
using uns_type = std::make_unsigned_t<K>;
using my_type = Impl;
using base = str_src_algs<K, StrRef, Impl, Mutable>;
str_src_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();
traits::copy(ptr, _str(), myLen);
return ptr + myLen;
}
/*!
* @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);
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
*/
constexpr size_t size() const {
return _len();
}
/*!
* @ru @brief Конвертировать в std::basic_string_view.
* @return std::basic_string_view<K>.
* @en @brief Convert to std::basic_string_view.
* @return std::basic_string_view<K>.
*/
template<typename D = K> requires is_equal_str_type_v<K, D>
constexpr std::basic_string_view<D> to_sv() const noexcept {
return {(const D*)_str(), _len()};
}
/*!
* @ru @brief Конвертировать в std::basic_string_view.
* @return std::basic_string_view<K>.
* @en @brief Convert to std::basic_string_view.
* @return std::basic_string_view<K>.
*/
template<typename D, typename Traits> requires is_equal_str_type_v<K, D>
constexpr operator std::basic_string_view<D, Traits>() const {
return {(const D*)_str(), _len()};
}
/*!
* @ru @brief Конвертировать в std::basic_string.
* @return std::basic_string<K>.
* @en @brief Convert to std::basic_string.
* @return std::basic_string<K>.
*/
template<typename D = K, typename Traits = std::char_traits<D>, typename Allocator = std::allocator<D>> requires is_equal_str_type_v<K, D>
constexpr std::basic_string<D, Traits, Allocator> to_string() const {
return {(const D*)_str(), _len()};
}
/*!
* @ru @brief Конвертировать в std::basic_string.
* @return std::basic_string<K>.
* @en @brief Convert to std::basic_string.
* @return std::basic_string<K>.
*/
template<typename D, typename Traits, typename Allocator> requires is_equal_str_type_v<K, D>
constexpr operator std::basic_string<D, Traits, Allocator>() const {
return {(const D*)_str(), _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.
*/
constexpr str_piece to_str() const noexcept {
return {_str(), _len()};
}
/*!
* @ru @brief Получить часть строки как "str_src".
* @param from - количество символов от начала строки.
* @param len - количество символов в получаемом "куске".
* @return Подстроку, str_src.
* @details Если `from` меньше нуля, то отсчитывается `-from` символов от конца строки в сторону начала.
* Если `len` меньше или равно нулю, то отсчитать `-len` символов от конца строки
* @en @brief Get part of a string as "str_src".
* @param from - number of characters from the beginning of the line.
* @param len - the number of characters in the resulting "chunk".
* @return Substring, str_src.
* @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 Подстроку, str_src.
* @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, str_src.
*/
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 Получить подстроку str_src с позиции от from до позиции to (не включая её).
* @details Для производительности метод никак не проверяет выходы за границы строки, используйте
* в сценариях, когда точно знаете, что это позиции внутри строки и to >= from.
* @param from - начальная позиция.
* @param to - конечная позиция (не входит в результат).
* @return Подстроку, str_src.
* @en @brief Get the substring str_src 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, str_src.
*/
constexpr str_piece from_to(size_t from, size_t to) const noexcept {
return str_piece{_str() + from, to - from};
}
/*!
* @ru @brief Получить подстроку str_src от начала и до первого найденного вхождения указанной подстроки.
* @details Если не найдено, вернёт всю строку.
* @param offset - позиция для начала поиска.
* @return Подстроку, str_src.
* @en @brief Get the substring str_src from the beginning to the first found occurrence of the specified substring.
* @details If not found, returns the entire string.
* @param offset - position to start the search.
* @return Substring, str_src.
*/
constexpr str_piece until(str_piece pattern, size_t offset = 0) const noexcept {
return (*this)(0, find_or_all(pattern, offset));
}
/*!
* @ru @brief Проверка на пустоту.
* @en @brief Check for emptiness.
*/
constexpr 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.
*/
constexpr 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>
constexpr 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>
constexpr 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
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr bool less_ia(str_piece text) const noexcept { // NOLINT
return compare_ia(text.symbols(), text.length()) < 0;
}
constexpr 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.
*/
constexpr 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...>
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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();
}
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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.
*/
constexpr 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>
constexpr 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>
constexpr 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);
}
template<typename To = std::vector<size_t>>
constexpr To find_all(const K* pattern, size_t patternLen, size_t offset, size_t maxCount) const {
To result;
for_all_finded([&](auto f) { result.emplace_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.
*/
template<typename To = std::vector<size_t>>
constexpr To find_all(str_piece pattern, size_t offset = 0, size_t maxCount = 0) const {
return find_all(pattern.symbols(), pattern.length(), offset, maxCount);
}
template<typename To = std::vector<size_t>>
constexpr void find_all_to(To& to, const K* pattern, size_t len, size_t offset = 0, size_t maxCount = 0) const {
return for_all_finded([&](size_t pos) {
to.emplace_back(pos);
}, pattern, len, 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.
*/
constexpr 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.
*/
constexpr size_t find_first_of(str_piece pattern, size_t offset = 0) const noexcept {
return std::basic_string_view<K>{_str(), _len()}.find_first_of(std::basic_string_view<K>{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.
*/
constexpr 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::basic_string_view<K>{text, _len()}.find_first_of(std::basic_string_view<K>{pattern.str, pattern.len}, offset);
return {fnd, fnd == std::basic_string<K>::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.
*/
constexpr size_t find_first_not_of(str_piece pattern, size_t offset = 0) const noexcept {
return std::basic_string_view<K>{_str(), _len()}.find_first_not_of(std::basic_string_view<K>{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.
*/
constexpr size_t find_last_of(str_piece pattern, size_t offset = str::npos) const noexcept {
return std::basic_string_view<K>{_str(), _len()}.find_last_of(std::basic_string_view<K>{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.
*/
constexpr 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::basic_string_view<K>{text, _len()}.find_last_of(std::basic_string_view<K>{pattern.str, pattern.len}, offset);
return {fnd, fnd == std::basic_string<K>::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.
*/
constexpr size_t find_last_not_of(str_piece pattern, size_t offset = str::npos) const noexcept {
return std::basic_string_view<K>{_str(), _len()}.find_last_not_of(std::basic_string_view<K>{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.
*/
constexpr 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.
*/
constexpr 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>
constexpr 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 convert_result<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 convert_result<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>
constexpr convert_result<T> to_int() const noexcept {
return int_convert::to_integer<K, T, Base, CheckOverflow, SkipWs, AllowSign>(_str(), _len());
}
/*!
* @ru @brief Преобразовать строку в double.
* @return std::optional<double>.
* @en @brief Convert string to double.
* @return std::optional<double>.
*/
template<bool SkipWS = true, bool AllowPlus = true> requires (sizeof(K) == 1)
std::optional<double> to_double() const noexcept {
size_t len = _len();
const K* ptr = _str();
if constexpr (SkipWS) {
while (len && uns_type(*ptr) <= ' ') {
len--;
ptr++;
}
}
if constexpr (AllowPlus) {
if (len && *ptr == K('+')) {
ptr++;
len--;
}
}
if (!len) {
return {};
}
double d{};
if (std::from_chars((const u8s*)ptr, (const u8s*)ptr + len, d).ec == std::errc{}) {
return d;
}
return {};
}
/*!
* @ru @brief Преобразовать строку в 16ричной записи в double. Пока работает только для char.
* @return std::optional<double>.
* @en @brief Convert string in hex form to double.
* @return std::optional<double>.
*/
template<bool SkipWS = true> requires (sizeof(K) == 1)
std::optional<double> to_double_hex() const noexcept {
size_t len = _len();
const K* ptr = _str();
if constexpr (SkipWS) {
while (len && uns_type(*ptr) <= ' ') {
len--;
ptr++;
}
}
if (len) {
double d{};
if (std::from_chars(ptr, ptr + len, d, std::chars_format::hex).ec == std::errc{}) {
return d;
}
}
return {};
}
/*!
* @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>
constexpr void as_number(T& t) const {
t = as_int<T>();
}
template<typename T, typename Op>
constexpr T splitf(const K* delimiter, size_t lendelimiter, 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(delimiter, lendelimiter, 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 + lendelimiter;
}
if constexpr (!std::is_same_v<T, void>) {
return results;
}
}
/*!
* @ru @brief Разделить строку на части по заданному разделителю, с возможным применением функтора к каждой подстроке.
* @tparam T - тип контейнера для складывания подстрок.
* @param delimiter - подстрока разделитель.
* @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 delimiter - 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>
constexpr T splitf(str_piece delimiter, const Op& beforeFunc, size_t offset = 0) const {
return splitf<T>(delimiter.symbols(), delimiter.length(), beforeFunc, offset);
}
/*!
* @ru @brief Разделить строку на подстроки по заданному разделителю.
* @tparam T - тип контейнера для результата.
* @param delimiter - разделитель.
* @param offset - позиция начала поиска разделителя.
* @return T - контейнер с результатом.
* @en @brief Split a string into substrings using a given delimiter.
* @tparam T - container type for the result.
* @param delimiter - delimiter.
* @param offset - the position to start searching for the separator.
* @return T - container with the result.
*/
template<typename T>
constexpr T split(str_piece delimiter, size_t offset = 0) const {
return splitf<T>(delimiter.symbols(), delimiter.length(), 0, offset);
}
// Начинается ли эта строка с указанной подстроки
// 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());
}
// Является ли эта строка началом указанной строки
// 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());
}
/*!
* @ru @brief Содержит ли строка только ASCII символы.
* @en @brief Whether the string contains only ASCII characters.
*/
constexpr bool is_ascii() const noexcept {
if (_is_empty())
return true;
if (std::is_constant_evaluated()) {
for (size_t idx = 0; idx < _len(); idx++) {
if (uns_type(_str()[idx]) > 127) {
return false;
}
}
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 upperred_only_ascii() const {
return R::upperred_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 Получить копию строки с заменёнными вхождениями подстрок.
* @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);
}
template<StrType<K> From>
constexpr static my_type make_trim_op(const From& from, const auto& opTrim) {
str_piece sfrom = from, newPos = opTrim(sfrom);
if (newPos.is_same(sfrom)) {
my_type res = from;
return res;
}
return my_type{newPos};
}
template<TrimSides S, StrType<K> From>
constexpr 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>
constexpr 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>
constexpr 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 - желаемый тип строки, по умолчанию str_src.
* @return R - строка, с удалёнными в начале и в конце пробельными символами.
* @en @brief Get a string with whitespace removed on the left and right.
* @tparam R - desired string type, default str_src.
* @return R - a string with whitespace characters removed at the beginning and end.
*/
template<typename R = str_piece>
constexpr R trimmed() const {
return R::template trim_static<TrimSides::TrimAll>(d());
}
/*!
* @ru @brief Получить строку с удалением пробельных символов слева.
* @tparam R - желаемый тип строки, по умолчанию str_src.
* @return R - строка, с удалёнными в начале пробельными символами.
* @en @brief Get a string with whitespace removed on the left.
* @tparam R - desired string type, default str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @return R - строка, с удалёнными в конце пробельными символами.
* @en @brief Get a string with whitespace removed on the right.
* @tparam R - desired string type, default str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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 - желаемый тип строки, по умолчанию str_src.
* @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 str_src.
* @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);
}
/*!
* @ru @brief Получить объект `Splitter` по заданному разделителю, который позволяет последовательно
* получать подстроки методом `next()`, пока `is_done()` false.
* @param delimiter - разделитель.
* @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 delimiter - delimiter.
* @return Splitter<K>.
*/
constexpr SplitterBase<K, str_piece> splitter(str_piece delimiter) const {
return SplitterBase<K, str_piece>{*this, delimiter};
}
};
template<size_t N> requires (N > 1)
struct find_all_container {
static constexpr size_t max_capacity = N;
size_t positions_[N];
size_t added_{};
constexpr void emplace_back(size_t pos) {
positions_[added_++] = pos;
}
};
/*!
* @ru @brief Простейший класс иммутабельной не владеющей строки.
* @details Этот класс заменяет `simple_str`, если вы используете только "strexpr.h".
* Аналог std::string_view. Содержит только указатель и длину.
* Как наследник от str_algs поддерживает все константные строковые методы,
* за исключением парсинга double из любых типов символов - поддерживаются только
* char, wchar_t, и совместимые с ними по размеру символы. Также не содержит
* работы с упрощённым юникодом.
* @tparam K - тип символов строки.
* @en @brief The simplest class of an immutable non-owning string.
* @details This class replaces `simple_str` if you only use `strexpr.h`.
* Analogous to std::string_view. Contains only a pointer and a length.
* As a descendant of str_algs, it supports all constant string methods,
* except for parsing double from any character types - only supported
* char, wchar_t, and characters compatible with them in size. Also does not contain
* work with simplified Unicode.
* @tparam K - the character type of the string.
*/
template<typename K>
struct str_src : str_src_algs<K, str_src<K>, str_src<K>, false> {
using symb_type = K;
using my_type = str_src<K>;
const symb_type* str;
size_t len;
str_src() = default;
/*!
* @ru @brief Конструктор из строкового литерала.
* @en @brief Constructor from a string literal.
*/
template<typename T, size_t N = const_lit_for<K, T>::Count>
constexpr str_src(T&& v) noexcept : str(v), len(N - 1) {}
/*!
* @ru @brief Конструктор из указателя и длины.
* @en @brief Constructor from pointer and length.
*/
constexpr str_src(const K* p, size_t l) noexcept : str(p), len(l) {}
template<StrType<K> T>
constexpr str_src(T&& t) : str(t.symbols()), len(t.length()){}
/*!
*@ru @brief Конструктор из std::basic_string.
*@en @brief Constructor from std::basic_string.
*/
template<typename A>
constexpr str_src(const std::basic_string<K, std::char_traits<K>, A>& s) noexcept : str(s.data()), len(s.length()) {}
/*!
*@ru @brief Конструктор из std::basic_string_view.
*@en @brief Constructor from std::basic_string_view.
*/
constexpr str_src(const std::basic_string_view<K, std::char_traits<K>>& 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.
*/
constexpr bool is_same(str_src<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.
*/
constexpr bool is_part_of(str_src<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.
*/
constexpr 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&.
*/
constexpr 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&.
*/
constexpr my_type& remove_suffix(size_t delta) {
len -= delta;
return *this;
}
};
/*!
* @ru @brief Класс, заявляющий, что ссылается на нуль-терминированную строку.
* @tparam K - тип символов строки.
* @details Упрощённая реализация simple_str_nt, когда вы используете только strexpr.h.
* Служит для показа того, что функция параметром хочет получить
* строку с нулем в конце, например, ей надо дальше передавать его в
* стороннее API. Без этого ей надо было бы либо указывать параметром
* конкретный класс строки, что лишает универсальности, либо приводило бы
* к постоянным накладным расходам на излишнее копирование строк во временный
* буфер. Источником нуль-терминированных строк могут быть строковые литералы
* при компиляции, либо классы, хранящие строки.
* @en @brief A class that claims to refer to a null-terminated string.
* @tparam K - the character type of the string.
* @details Simplified implementation of simple_str_nt when you only use strexpr.h.
* 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 str_src_nt : str_src<K>, null_terminated<K, str_src_nt<K>> {
using symb_type = K;
using my_type = str_src_nt<K>;
using base = str_src<K>;
constexpr static const K empty_string[1] = {0};
str_src_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>
constexpr explicit str_src_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 Конструктор из строкового литерала.
* @en @brief Constructor from a string literal.
*/
template<typename T, size_t N = const_lit_for<K, T>::Count>
constexpr str_src_nt(T&& v) noexcept : base(std::forward<T>(v)) {}
/*!
* @ru @brief Конструктор из указателя и длины.
* @en @brief Constructor from pointer and length.
*/
constexpr str_src_nt(const K* p, size_t l) noexcept : base(p, l) {}
template<StrType<K> T>
constexpr str_src_nt(T&& t) {
base::str = t.symbols();
base::len = t.length();
}
/*!
*@ru @brief Конструктор из std::basic_string.
*@en @brief Constructor from std::basic_string.
*/
template<typename A>
constexpr str_src_nt(const std::basic_string<K, std::char_traits<K>, A>& s) noexcept : base(s) {}
static const my_type empty_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.
*/
constexpr 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 str_src_nt<K> str_src_nt<K>::empty_str{str_src_nt<K>::empty_string, 0};
template<typename K> struct simple_str_selector;
inline namespace literals {
/*!
* @ingroup StrExprs
* @ru @brief Создает набор из основания системы счисления и флагов, который может быть применён к целым
* числам для задания параметров форматирования с помощью оператора деления: `num / 0xПараметрыФорматирования_fmt`.
* Параметры форматирования задаются следующим образом: сначала идёт `0x`, затем основание счисления, записанное в
* десятичном виде. Далее могут идти символы, обозначающие различные флаги:
* - b при задании ширины поля выравнивать влево, аналог f::l.
* - d при задании ширины поля выравнивать вправо, аналог f::r.
* - c при задании ширины поля выравнивать по центру, аналог f::c.
* - a выводить префикс системы счисления, 0b для 2, 0 для 8, 0x для 16, аналог f::p.
* - A выводить префикс системы счисления, 0B для 2, 0 для 8, 0X для 16, аналог f::P.
* - 0 дополнять число до заданной ширины нулями слева. Не совместимо с опциями выравнивания, аналог f::z.
* - E Для систем счисления более 10, выводить символы в верхнем регистре, аналог f::u.
* - e Для знаковых типов чисел для положительных значений выводить '+' перед числом, аналог f::sp.
* - f Для знаковых типов чисел для положительных значений выводить пробел перед числом, аналог f::ss.
* - FКодCимволаВhex Задаёт код символа-заполнителя при указании ширины поля, аналог f::f<'c'>.
* - Число в десятичном виде, начинающееся не с 0. Задаёт ширину поля, аналог f::w<N>.
* При указании ширины можно либо указать желаемое выравнивание (b, c, d),
* либо дополнение нулями (0), но не оба сразу. Если ничего из этого не указано, применяется выравнивание вправо.
* Число дополняется до заданной ширины, если оно короче. Если длиннее, то выводится всё число.
* - ' разделитель, пропускается.
*
* Так как после `F` все символы воспринимаются как hex код символа разделителя, при необходимости его использования
* лучше ставить его в конце литерала форматирования, или отделять '.
* <br/>
* @en @brief Creates a set of radix and flags that can be applied to integers
* numbers to set formatting parameters using the division operator: `num / 0xFormatOptions_fmt`.
* Formatting parameters are set as follows: first comes `0x`, then the radix written in
* decimal form. Next may be symbols indicating various flags:
* - b when setting the field width, align to the left, analogous to f::l.
* - d when setting the field width, align to the right, analogous to f::r.
* - c when setting the field width, align to the center, analogous to f::c.
* - a display the number system prefix, 0b for 2, 0 for 8, 0x for 16, analogous to f::p.
* - A display the number system prefix, 0B for 2, 0 for 8, 0X for 16, analogous to f::P.
* - 0 pad the number to the specified width with zeros on the left. Not compatible with alignment options, similar to f::z.
* - E For radix greater than 10, display characters in uppercase, analogous to f::u.
* - e For signed number types, for positive values, print '+' before the number, analogous to f::sp.
* - f For signed number types, for positive values, print a space before the number, analogous to f::ss.
* - FCodeInhex Sets the code of the filler character when specifying the field width, analogous to f::f<'c'>.
* - Number in decimal form, not starting from 0. Sets the field width, analogous to f::w<N>.
* When specifying the width, you can either specify the desired alignment (b, c, d),
* or zero padding (0), but not both. If none of these are specified, right alignment is applied.
* The number is padded to the given width if it is shorter. If it is longer, then the entire number is displayed.
* - ' delimiter, skipped.
*
* Since after `F` all characters are perceived as hex code of the delimiter character, if necessary, use it
* it is better to put it at the end of format literal, or separate with '.
* <br/>
* @ru Пример: @en Example: @~
* ```cpp
* stringu u16t = u"Number "_ss + num
* + " in octal is " + num / 0x8'016_fmt // same as e_int<8, f::w<16> | f::z>(num)
* + ", in binary is " + num / 0x2a032_fmt; // same as e_int<2, f::w<32> | f::p | f::z>(num);
* ....
* stringa text = "Count is "_ss + count / 0x16A08E_fmt; // same as e_int<16, f::P | f::z | f::w<8> | f::u>(count)
* ....
* stringa text = "Number "_ss + count / 0x16c20EF5F_fmt; // same as e_int<16, f::c | f::w<20> | f::u | f::f<'_'>>(count)
* ```
*/
template<char...Chars>
SS_CONSTEVAL auto operator""_fmt() {
return f::skip_0x<Chars...>();
}
} // namespace literals
#ifndef IN_FULL_SIMSTR
template<typename K>
using simple_str = str_src<K>;
template<typename K>
struct simple_str_selector {
using type = simple_str<K>;
};
template<typename K>
using simple_str_nt = str_src_nt<K>;
template<typename K>
using Splitter = SplitterBase<K, str_src<K>>;
using ssa = str_src<u8s>;
using ssb = str_src<ubs>;
using ssw = str_src<wchar_t>;
using ssu = str_src<u16s>;
using ssuu = str_src<u32s>;
using stra = str_src_nt<u8s>;
using strb = str_src_nt<ubs>;
using strw = str_src_nt<wchar_t>;
using stru = str_src_nt<u16s>;
using struu = str_src_nt<u32s>;
template<typename K>
consteval simple_str_nt<K> select_str(simple_str_nt<u8s> s8, simple_str_nt<ubs> sb, 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, ubs>)
return sb;
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, u8##p, L##p, u##p, U##p)
inline namespace literals {
/*!
* @ru @brief Оператор литерал в str_src.
* @param ptr - указатель на строку.
* @param l - длина строки.
* @return str_src.
* @en @brief Operator literal in str_src.
* @param ptr - pointer to a string.
* @param l - string length.
* @return str_src.
*/
SS_CONSTEVAL str_src_nt<u8s> operator""_ss(const u8s* ptr, size_t l) {
return str_src_nt<u8s>{ptr, l};
}
/*!
* @ru @brief Оператор литерал в str_src.
* @param ptr - указатель на строку.
* @param l - длина строки.
* @return str_src.
* @en @brief Operator literal in str_src.
* @param ptr - pointer to a string.
* @param l - string length.
* @return str_src.
*/
SS_CONSTEVAL str_src_nt<ubs> operator""_ss(const ubs* ptr, size_t l) {
return str_src_nt<ubs>{ptr, l};
}
/*!
* @ru @brief Оператор литерал в str_src.
* @param ptr - указатель на строку.
* @param l - длина строки.
* @return str_src.
* @en @brief Operator literal in str_src.
* @param ptr - pointer to a string.
* @param l - string length.
* @return str_src.
*/
SS_CONSTEVAL str_src_nt<uws> operator""_ss(const uws* ptr, size_t l) {
return str_src_nt<uws>{ptr, l};
}
/*!
* @ru @brief Оператор литерал в str_src.
* @param ptr - указатель на строку.
* @param l - длина строки.
* @return str_src.
* @en @brief Operator literal in str_src.
* @param ptr - pointer to a string.
* @param l - string length.
* @return str_src.
*/
SS_CONSTEVAL str_src_nt<u16s> operator""_ss(const u16s* ptr, size_t l) {
return str_src_nt<u16s>{ptr, l};
}
/*!
* @ru @brief Оператор литерал в str_src.
* @param ptr - указатель на строку.
* @param l - длина строки.
* @return str_src.
* @en @brief Operator literal in str_src.
* @param ptr - pointer to a string.
* @param l - string length.
* @return str_src.
*/
SS_CONSTEVAL str_src_nt<u32s> operator""_ss(const u32s* ptr, size_t l) {
return str_src_nt<u32s>{ptr, l};
}
} // namespace literals
#endif
template<typename K, bool withSpaces>
struct CheckSpaceTrim {
constexpr bool is_trim_spaces(K s) const {
return s == ' ' || (s >= 9 && s <= 13); // || isspace(s);
}
};
template<typename K>
struct CheckSpaceTrim<K, false> {
constexpr bool is_trim_spaces(K) const {
return false;
}
};
template<typename K>
struct CheckSymbolsTrim {
str_src<K> symbols;
constexpr bool is_trim_symbols(K s) const {
return symbols.len != 0 && str_src<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)) {}
constexpr bool is_trim_symbols(K s) const noexcept {
return symbols.contain(s);
}
};
template<typename K>
struct CheckConstSymbolsTrim<K, 0> {
constexpr 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> {
constexpr bool isTrim(K s) const {
return CheckSpaceTrim<K, withSpaces>::is_trim_spaces(s) || SymbSelector<K, N>::type::is_trim_symbols(s);
}
constexpr str_src<K> operator()(str_src<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>;
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>
constexpr inline auto trimOp(T&& pattern) {
return trim_operator<S, K, N - 1, withSpaces>{pattern};
}
template<TrimSides S = TrimSides::TrimAll, bool withSpaces = false, typename K>
constexpr inline auto trimOp(str_src<K> pattern) {
return trim_operator<S, K, 0, withSpaces>{pattern};
}
static constexpr size_t FIND_CACHE_SIZE = 16;
template<typename K, size_t N, size_t L>
struct expr_replaces : expr_to_std_string<expr_replaces<K, N, L>> {
using symb_type = K;
using my_type = expr_replaces<K, N, L>;
str_src<K> what;
const K(&pattern)[N + 1];
const K(&repl)[L + 1];
mutable find_all_container<FIND_CACHE_SIZE> matches_;
mutable size_t last_;
constexpr expr_replaces(str_src<K> w, const K(&p)[N + 1], const K(&r)[L + 1]) : what(w), pattern(p), repl(r) {}
constexpr size_t length() const {
size_t l = what.length();
if constexpr (N == L) {
return l;
}
what.find_all_to(matches_, pattern, N, 0, FIND_CACHE_SIZE);
if (matches_.added_) {
last_ = matches_.positions_[matches_.added_ - 1] + N;
l += int(L - N) * matches_.added_;
if (matches_.added_ == FIND_CACHE_SIZE) {
for (;;) {
size_t next = what.find(pattern, N, last_);
if (next == str::npos) {
break;
}
last_ = next + N;
l += L - N;
}
}
}
if (!l) {
matches_.added_ = -1;
}
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 (matches_.added_ == 0) {
return what.place(ptr);
} else if (matches_.added_ == -1) {
// after replaces text become empty
return ptr;
}
const K* from = what.symbols();
for (size_t start = 0, offset = matches_.positions_[0], idx = 1; ;) {
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 = idx < FIND_CACHE_SIZE ? matches_.positions_[idx++] : 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 string.
* @param p - string literal, searched substring.
* @param r - string literal, what to replace with.
*/
template<StrSource A, typename K = symb_type_from_src_t<A>, 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)
constexpr auto e_repl(A&& w, T&& p, X&& r) {
return expr_replaces<K, N - 1, L - 1>{std::forward<A>(w), p, r};
}
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки на другую строку.
* @tparam K - тип строки.
* @en @brief A string expression that generates a string replacing all occurrences of the given substring to another string.
* @tparam K - string type.
*/
template<typename K>
struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
using symb_type = K;
using my_type = expr_replaced<K>;
str_src<K> what;
const str_src<K> pattern;
const str_src<K> repl;
mutable find_all_container<FIND_CACHE_SIZE> matches_;
mutable size_t 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(str_src<K> w, str_src<K> p, str_src<K> r) : what(w), pattern(p), repl(r) {}
constexpr size_t length() const {
size_t l = what.length(), plen = pattern.length(), rlen = repl.length();
if (!plen || plen == rlen) {
return l;
}
what.find_all_to(matches_, pattern.symbols(), plen, 0, FIND_CACHE_SIZE);
if (matches_.added_) {
last_ = matches_.positions_[matches_.added_ - 1] + plen;
l += int(rlen - plen) * matches_.added_;
if (matches_.added_ == FIND_CACHE_SIZE) {
for (;;) {
size_t next = what.find(pattern.symbols(), plen, last_);
if (next == str::npos) {
break;
}
last_ = next + plen;
l += rlen - plen;
}
}
}
if (!l) {
matches_.added_ = -1;
}
return l;
}
constexpr K* place(K* ptr) const noexcept {
size_t plen = pattern.length(), rlen = repl.length();
if (plen == rlen) {
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(), rlen);
ptr += rlen;
start = next + plen;
}
return ptr;
}
if (matches_.added_ == 0) {
return what.place(ptr);
} else if (matches_.added_ == -1) {
// after replaces text become empty
return ptr;
}
const K* from = what.symbols();
for (size_t start = 0, offset = matches_.positions_[0], idx = 1; ;) {
ch_traits<K>::copy(ptr, from + start, offset - start);
ptr += offset - start;
ch_traits<K>::copy(ptr, repl.symbols(), rlen);
ptr += rlen;
start = offset + plen;
if (start >= last_) {
size_t tail = what.length() - start;
ch_traits<K>::copy(ptr, from + start, tail);
ptr += tail;
break;
} else {
offset = idx < FIND_CACHE_SIZE ? matches_.positions_[idx++] : what.find(pattern.symbols(), plen, start);
}
}
return ptr;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки на строковое выражение.
* @tparam K - тип строки.
* @details Если искомая подстрока не найдена, то строковое выражение даже не вычисляется.
* Затем при осуществлении замены строковое выражение вычисляется только один раз в место первой замены,
* а в следующие места замен просто копируется символы из первого места. Это позволяет экономить память
* и время, если вам надо сделать замену на какую-либо "сборную" строку.
* @en @brief A string expression that generates a string replacing all occurrences of the given substring to string expression.
* @tparam K - string type.
* @details If the search substring is not found, the string expression is not even evaluated.
* Then, when performing a replacement, the string expression is evaluated only once at the first replacement location,
* and characters from the first location are simply copied to subsequent replacement locations. This saves memory
* and time if you need to replace with some kind of "composite" string.
*/
template<typename K, StrExprForType<K> E>
struct expr_replaced_e : expr_to_std_string<expr_replaced_e<K, E>> {
using symb_type = K;
using my_type = expr_replaced<K>;
str_src<K> what;
const str_src<K> pattern;
mutable size_t replLen;
mutable find_all_container<FIND_CACHE_SIZE> matches_;
mutable size_t last_;
const E& expr;
/*!
* @ru @brief Конструктор.
* @param w - исходная строка.
* @param p - искомая подстрока.
* @param e - строковое выражение для замены.
* @en @brief Constructor.
* @param w - source string.
* @param p - the searched substring.
* @param e - string expression to replace.
*/
constexpr expr_replaced_e(str_src<K> w, str_src<K> p, const E& e) : what(w), pattern(p), expr(e) {}
constexpr size_t length() const {
size_t l = what.length(), plen = pattern.length();
if (!plen) {
return l;
}
matches_.positions_[0] = what.find(pattern);
if (matches_.positions_[0] == -1) {
// Не нашли вхождений, нечего менять
return l;
}
matches_.added_ = 1;
// Вхождение есть, надо теперь получить длину замены
replLen = expr.length();
if (replLen == plen) {
// Замена той же длины, общая длина не изменится
return l;
}
what.find_all_to(matches_, pattern.symbols(), plen, matches_.positions_[0] + plen, FIND_CACHE_SIZE - 1);
last_ = matches_.positions_[matches_.added_ - 1] + plen;
l += int(replLen - plen) * matches_.added_;
if (matches_.added_ == FIND_CACHE_SIZE) {
for (;;) {
size_t next = what.find(pattern.symbols(), plen, last_);
if (next == str::npos) {
break;
}
last_ = next + plen;
l += replLen - plen;
}
}
if (!l) {
matches_.added_ = -1;
}
return l;
}
constexpr K* place(K* ptr) const noexcept {
if (matches_.added_ == 0) {
// не было найдено вхождений
return what.place(ptr);
} else if (matches_.added_ == -1) {
// Строка стала пустой
return ptr;
}
size_t plen = pattern.length();
const K* from = what.symbols();
ch_traits<K>::copy(ptr, from, matches_.positions_[0]);
ptr += matches_.positions_[0];
const K* repl = ptr;
expr.place((typename E::symb_type*)ptr);
ptr += replLen;
size_t start = matches_.positions_[0] + plen;
if (plen == replLen) {
for (;;) {
size_t next = what.find(pattern, start);
if (next == str::npos) {
break;
}
size_t delta = next - start;
ch_traits<K>::copy(ptr, from + start, delta);
ptr += delta;
ch_traits<K>::copy(ptr, repl, replLen);
ptr += replLen;
start = next + plen;
}
} else {
for (size_t idx = 1;;) {
if (start >= last_) {
break;
}
size_t next = idx < FIND_CACHE_SIZE ? matches_.positions_[idx++] : what.find(pattern, start);
size_t delta = next - start;
ch_traits<K>::copy(ptr, from + start, delta);
ptr += delta;
ch_traits<K>::copy(ptr, repl, replLen);
ptr += replLen;
start = next + plen;
}
}
size_t tail = what.length() - start;
ch_traits<K>::copy(ptr, from + start, tail);
return ptr + tail;
}
};
/*!
* @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 string.
* @param p - string object, searched substring, maybe runtime.
* @param r - string object, replace substring, maybe runtime.
*/
template<StrSource A, typename K = symb_type_from_src_t<A>, typename T, typename X>
requires (std::is_constructible_v<str_src<K>, T> && std::is_constructible_v<str_src<K>, X> && (!is_const_lit_v<T> || !is_const_lit_v<X>))
constexpr auto e_repl(A&& w, T&& p, X&& r) {
str_src<K> pattern{std::forward<T>(p)};
str_src<K> repl{std::forward<X>(r)};
return expr_replaced<K>{std::forward<A>(w), pattern, repl};
}
/*!
* @ingroup StrExprs
* @ru @brief Получить строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки.
* @tparam K - тип символа, выводится из первого аргумента.
* @param w - начальная строка.
* @param p - строковый объект, искомая подстрока, может быть рантайм.
* @param expr - строковое выражение, на что заменять.
* @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 string.
* @param p - string object, searched substring, maybe runtime.
* @param expr - string expression, what to replace with.
*/
template<StrSource A, typename K = symb_type_from_src_t<A>, typename T, StrExprForType<K> E>
requires std::is_constructible_v<str_src<K>, T>
constexpr auto e_repl(A&& w, T&& p, const E& expr) {
str_src<K> pattern{std::forward<T>(p)};
return expr_replaced_e<K, E>{std::forward<A>(w), pattern, expr};
}
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>> {};
// Строковое выражение для замены символов
// String expression to replace characters
template<typename K, size_t N, bool UseVectorForReplace>
struct expr_replace_const_symbols : expr_to_std_string<expr_replace_const_symbols<K, N, UseVectorForReplace>> {
using symb_type = K;
inline static const int BIT_SEARCH_TRESHHOLD = 4;
const K pattern_[N];
const str_src<K> source_;
const str_src<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(str_src<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, str_src<K> source, K s, str_src<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, str_src<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, '\"', "&quot;", '<', "&lt;", '\'', "&#39;", '&', "&amp;") + "</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, '\"', "&quot;", '<', "&lt;", '\'', "&#39;", '&', "&amp;");
* }
* ....
* out += "<div>" + repl_html_symbols(content) + "</div>";
* ```
*/
template<bool UseVector = false, StrSource A, typename K = symb_type_from_src_t<A>, typename ... Repl>
requires (sizeof...(Repl) % 2 == 0)
auto e_repl_const_symbols(A&& src, Repl&& ... other) {
return expr_replace_const_symbols<K, sizeof...(Repl) / 2, UseVector>(std::forward<A>(src), std::forward<Repl>(other)...);
}
/*!
* @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 : expr_to_std_string<expr_replace_symbols<K, UseVectorForReplace>> {
using symb_type = K;
using str_t = typename simple_str_selector<K>::type;
inline static const int BIT_SEARCH_TRESHHOLD = 4;
const str_src<K> source_;
const std::vector<std::pair<K, str_t>>& replaces_;
std::basic_string<K, ch_traits<K>, std::allocator<K>> 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, {
{'-', ""},
{'<', "&lt;"},
{'>', "&gt;"},
{'\'', "&#39;"},
{'\"', "&quot;"},
{'&', "&amp;"},
}};
* ```
* @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(str_t source, const std::vector<std::pair<K, str_t>>& repl )
: source_(source), replaces_(repl)
{
size_t pattern_len = replaces_.size();
pattern_.resize(pattern_len);
K* pattern = pattern_.data();
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};
}
};
template<typename K, StrExprForType<K> T>
struct force_copy {
const T& t_;
force_copy(const T& t) : t_(t){}
};
template<typename K, typename T>
struct symb_type_from_src<force_copy<K, T>> {
using type = K;
};
template<StrExpr T>
force_copy(T&&) -> force_copy<typename T::symb_type, T>;
template<typename K, typename T>
constexpr auto to_subst(T&& t) {
return to_strexpr<K>(std::forward<T>(t));
}
template<typename K, typename T, size_t N>
constexpr auto to_subst(const T(&t)[N]) {
return expr_literal<T, N - 1>{t};
}
template<typename K, StrExprForType<K> T>
constexpr decltype(auto) to_subst(T&& t) {
return std::forward<T>(t);
}
template<typename K, typename T>
constexpr T to_subst(const force_copy<K, T>& t) {
return t.t_;
}
template<typename K, typename T>
constexpr T to_subst(force_copy<K, T>&& t) {
return t.t_;
}
template<typename K, typename T>
constexpr T to_subst(force_copy<K, T>& t) {
return t.t_;
}
template<typename K, typename T>
using to_str_exp_t = decltype(to_subst<K>(std::declval<T>()));
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражения, объединяющее указанные строковые выражения, с использованием заданного разделителя.
* @tparam K - тип символов, выводится из типа разделителя.
* @en @brief String expression concatenating the specified string expressions using the specified delimiter.
* @tparam K - character type, deduced from the separator type.
*/
template<typename K, typename G, typename Arg, typename...Args>
struct e_concat : expr_to_std_string<e_concat<K, G, Arg, Args...>> {
using symb_type = K;
using store_t = std::tuple<to_str_exp_t<K, Args>...>;
using arg_t = to_str_exp_t<K, Arg>;
to_str_exp_t<K, G> glue_;
arg_t arg_;
store_t args_;
mutable size_t glue_len_;
/*!
* @ru @brief Создание строкового выражения, объединяющего указанные строковые выражения, с использованием заданного разделителя.
* @param glue - "клей", используемый при соединении аргументов, вставляется между ними.
* @param arg, args... - объединяемые аргументы, не менее двух.
* @details "Склеивает" переданные аргументы, вставляя между ними заданный "клей".
* Соединителем и аргументами могут быть строковые литералы, строковые выражения, стандартные строки.
* Аргументами также могут быть любые типы, для которых есть преобразование в строковое выражение.
* (см. @ref ConvertToStrExpr "Конвертация типов в в строковые выражения").
* Для аргументов, которые сами являются строковыми выражениями, `e_concat` сохраняет только ссылку на них.
* Обычно это не является проблемой, если ссылка не на временный объект, или строковое выражение материализуется
* сейчас же, до ';'. Если же вам необходимо вернуть `e_concat` как строковое выражение из функции,
* можно заставить его сохранить аргументы строковые выражения по копии, обернув их в `force_copy{}`.
* См. пример в tests/test_tostrexpr.cpp, Method4.
* @en @brief Create a string expression concatenating the specified string expressions using the specified delimiter.
* @param glue - the "glue" used when connecting arguments is inserted between them.
* @param arg, args... - the arguments to be combined, at least two.
* @details "Glues" the passed arguments, inserting the specified "glue" between them.
* The connector and arguments can be string literals, string expressions, standard strings.
* Arguments can also be any type for which there is a conversion to a string expression.
* (see @ref ConvertToStrExpr "Converting types to string expressions").
* For arguments that are themselves string expressions, `e_concat` stores only a reference to them.
* This is usually not a problem if the reference is not to a temporary object, or the string expression is materialized
* now, before ';'. If you need to return `e_concat` as a string expression from a function,
* you can force it to preserve string expression arguments over a copy by wrapping them in `force_copy{}`.
* See tests/test_tostrexpr.cpp, Method4 for an example.
* @ru Пример: @en Example @~
* ```cpp
* std::string t = e_concat("", text, " = ", count, " times.");
* ....
* std::string t = "msg=" + e_concat(", ", text1, text3, text3, count1, count2);
* ```
*/
constexpr e_concat(G&& glue, Arg&& arg, Args&&...args)
: glue_(to_subst<K>(std::forward<G>(glue)))
, arg_(to_subst<K>(std::forward<Arg>(arg)))
, args_(to_subst<K>(std::forward<Args>(args))...) {}
constexpr size_t length() const noexcept {
return [this]<size_t...Indexes>(std::index_sequence<Indexes...>) {
glue_len_ = glue_.length();
size_t l = arg_.length() + glue_len_ * sizeof...(Args);
((l += std::get<Indexes>(args_).length()),...);
return l;
}(std::make_index_sequence<sizeof...(Args)>());
}
constexpr K* place(K* ptr) const noexcept {
return [this]<size_t...Indexes>(K* ptr, std::index_sequence<Indexes...>) {
ptr = (K*)arg_.place((typename std::remove_cvref_t<arg_t>::symb_type*)ptr);
const K* glueStart = ptr;
ptr = glue_.place(ptr);
(
(
ptr = (K*)std::get<Indexes>(args_).place((typename std::remove_cvref_t<std::tuple_element_t<Indexes, store_t>>::symb_type*)ptr),
glue_len_ > 0 && Indexes < sizeof...(Args) - 1 ? (ch_traits<K>::copy(ptr, glueStart, glue_len_), ptr += glue_len_) : nullptr
),
...);
return ptr;
}(ptr, std::make_index_sequence<sizeof...(Args)>());
}
};
// CTAD deducing rule for e_concat
template<typename T, typename ... Args> requires (sizeof...(Args) > 1)
e_concat(T&&, Args&&...) -> e_concat<symb_type_from_src_t<T>, T, Args...>;
struct parse_subst_string_error {
parse_subst_string_error(const char*){}
};
namespace details {
template<typename K, size_t NParams>
constexpr size_t parse_pattern_string(str_src<K> pattern, auto&& add_part, auto&& add_param) {
char used_args[NParams] = {0};
const K* first = pattern.begin(), *last = pattern.end(), *start = first;
size_t all_len = 0;
auto find = [](const K* from, const K* last, K s) {
while (from != last) {
if (*from == s) {
break;
}
from++;
}
return from;
};
size_t idx_in_params = 0;
while (first != last) {
const K* open_pos = first;
if (*first != '{') {
open_pos = find(first, last, '{');
for (;;) {
const K* close_pos = find(first, open_pos, '}');
if (close_pos == open_pos) {
unsigned len = open_pos - first;
add_part(first - start, len);
all_len += len;
break;
}
++close_pos;
if (close_pos == open_pos || *close_pos != '}') {
throw parse_subst_string_error{"unescaped }"};
}
unsigned len = close_pos - first;
add_part(first - start, len);
all_len += len;
first = ++close_pos;
}
if (open_pos == last) {
break;
}
}
if (++open_pos == last) {
throw parse_subst_string_error{"unescaped {"};
}
if (*open_pos == '}') {
if (idx_in_params == -1) {
throw parse_subst_string_error{"already used param ids"};
}
if (idx_in_params == NParams) {
throw parse_subst_string_error{"too many params"};
}
used_args[idx_in_params] = 1;
add_param(idx_in_params++);
first = open_pos + 1;
} else if (*open_pos == '{') {
add_part(open_pos - start, 1);
all_len++;
first = open_pos + 1;
} else {
if (idx_in_params != 0 && idx_in_params != -1) {
throw parse_subst_string_error{"already used non id params"};
}
idx_in_params = -1;
const K* end = find(open_pos, last, '}');
if (end == last) {
throw parse_subst_string_error{"not found }"};
}
auto [p, err, _] = str_src<K>(open_pos, end - open_pos).template to_int<unsigned, true, 10, false, false>();
if (err != IntConvertResult::Success || p < 1 || p > NParams) {
throw parse_subst_string_error{"bad param id"};
}
used_args[--p] = 1;
add_param(p);
first = end + 1;
}
}
for (auto c : used_args) {
if (!c) {
throw parse_subst_string_error{"unused param"};
}
}
return all_len;
}
struct portion {
unsigned start: 16;
unsigned len: 15;
unsigned is_param: 1;
portion() = default;
constexpr void set_param(unsigned param) {
if (param >= (1 << 16)) {
throw parse_subst_string_error{"the parameter id is too large"};
}
start = param;
is_param = 1;
}
constexpr void set_part(unsigned from, unsigned l) {
if (from >= (1 << 16) || len >= (1 << 15)) {
throw parse_subst_string_error{"the string part is too large"};
}
start = from;
len = l;
is_param = 0;
}
};
template<typename K, size_t PtLen, size_t NParams>
struct subst_params {
const K(&source_)[PtLen];
unsigned all_len_{};
unsigned actual_{};
// The pattern string can be divided into a maximum of this number of portions.
// "a{}a{}a{}a" - One portion of one symbol from the edge, and two portions for every three symbols
portion portions_[1 + ((PtLen - 2) * 2 / 3)]{};
consteval subst_params(const K(&pattern)[PtLen]) : source_(pattern) {
all_len_ = parse_pattern_string<K, NParams>(pattern,
[this](unsigned from, unsigned len) {
portions_[actual_++].set_part(from, len);
}, [this](unsigned param) {
portions_[actual_++].set_param(param);
});
}
};
} // namespace details
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение, которое подставляет в заданные места в строковом литерале - образце значения переданных строковых выражений.
* @en @brief String expression that substitutes the values of the passed string expressions into the specified places in a string literal.
*/
template<typename K, size_t PtLen, typename ... Args>
struct e_subst : expr_to_std_string<e_subst<K, PtLen, Args...>> {
inline static constexpr size_t NParams = sizeof...(Args);
using symb_type = K;
using store_t = std::tuple<to_str_exp_t<K, Args>...>;
const details::subst_params<K, PtLen, NParams>& subst_;
store_t args_;
/*!
* @ru @brief Создает строковое выражение, которое подставляет в заданные места в строковом литерале - образце значения переданных строковых выражений.
* @param pattern - распарсенная во время компиляции информация составе строки-шаблона, содержит длины текстовых порций и места вставки параметров.
* Создается автоматически из переданного строкового литерала.
* @param args... - аргументы, которые будут подставляться в заданные места образца. Также, как и в `e_concat`, могут быть строковые литералы,
* строковые выражения, стандартные строки, а также любые типы, для которых есть преобразование в строковое выражение.
* @details Функция создаёт строковое выражение, которое при материализации генерирует текст из образца, подставляя в места подстановки
* значения переданных аргументов. Строка-образец задается строковым литералом, константной времени компиляции.
* Места вставки обозначаются либо как `{}`, либо как `{номер}`.
* В случае без указания номера, параметры подставляются в переданном в функцию порядке. В случае указания номера, параметры подставляются в соответствии
* с указанным порядковым номером. Нумерация параметров начинается с 1. Смешивать параметры без номера и с номером нельзя - используется только
* один из вариантов для всех подстановок. В случае указания номеров - один параметр может участвовать в нескольких подстановках.
* Все переданные параметры должны участвовать в подстановках. Для вставки самих фигурных скобок они должны удваиваться - `{{`, `}}`.
* Строка-образец обрабатывается во время компиляции, и для неё сразу создаётся массив с информацией о вставках - какие части строки копировать
* в результат, в какие места вставлять переданные значения.
* Функция не является заменой `std::format`, не работает с образцом, задаваемым в рантайм, и не поддерживает каких-либо параметров форматирования
* подставляемых значений. Все передаваемые аргументы должны сами уметь преобразовывать себя в строковые выражения
* (см. @ref ConvertToStrExpr "Конвертация типов в в строковые выражения").
* @en @brief Creates a string expression that substitutes the values of the passed string expressions into the specified places in a string literal.
* @param pattern - information parsed during compilation in the template string, containing the lengths of text chunks and places to insert parameters.
* Created automatically from the passed string literal.
* @param args... - arguments that will be inserted into the specified places in the sample. Also, as in `e_concat`, there can be string literals,
* string expressions, standard strings, as well as any types for which there is a conversion to a string expression.
* @details The function creates a string expression, which, when materialized, generates text from the sample, substituting it in placeholders
* values of the passed arguments. The pattern string is specified by a string literal, a compile-time constant.
* Insertion locations are indicated by either `{}` or `{number}`.
* In case without spectacle number, the parameters are submitted to the order of order. In case of the index number, the parameters are submitted in accordance with
* with the specified serial number. The numbering of parameters starts from 1. You cannot mix parameters without a number and with a number - only used
* one of the options for all substitutions. In the case of specifying numbers, one parameter can participate in several substitutions.
* All passed parameters must participate in substitutions. To insert curly braces themselves, they must be doubled - `{{`, `}}`.
* The sample string is processed during compilation, and an array is immediately created for it with information about insertions - which parts of the string to copy
* in the result, where to insert the passed values.
* The function is not a replacement for `std::format`, does not work with the sample specified at runtime, and does not support any formatting options
* for substituted values. All passed arguments must be able to convert themselves to string expressions
* (see @ref ConvertToStrExpr "Converting types to string expressions").
* @ru Пример: @en Example: @~
* ```cpp
* lstringu<100> u16t = e_subst(u"Test {} from {}, {}.", from, total, success ? u"success"_ss : u"fail"_ss);
* ```
*/
constexpr e_subst(const details::subst_params<K, PtLen, NParams>& subst, Args&&...args)
: subst_(subst)
, args_(to_subst<K>(std::forward<Args>(args))...){}
constexpr size_t length() const noexcept {
return [this]<size_t...Indexes>(std::index_sequence<Indexes...>) {
size_t idx = 0;
size_t expr_length_[NParams] = {};
((expr_length_[idx++] = std::get<Indexes>(args_).length()),...);
size_t l = subst_.all_len_;
for (idx = 0; idx < subst_.actual_; idx++) {
if (subst_.portions_[idx].is_param) {
l += expr_length_[subst_.portions_[idx].start];
}
}
return l;
}(std::make_index_sequence<sizeof...(Args)>());
}
template<size_t Idx>
constexpr K* place_idx(K* ptr, size_t idx) const noexcept {
if (idx == Idx) {
return (K*)std::get<Idx>(args_).place((typename std::remove_cvref_t<std::tuple_element_t<Idx, store_t>>::symb_type*)ptr);
}
if constexpr (Idx < NParams - 1) {
return place_idx<Idx + 1>(ptr, idx);
}
return ptr;
}
constexpr K* place(K* ptr) const noexcept {
for (size_t idx = 0; idx < subst_.actual_; idx++) {
if (subst_.portions_[idx].is_param) {
ptr = place_idx<0>(ptr, subst_.portions_[idx].start);
} else {
ch_traits<K>::copy(ptr, subst_.source_ + subst_.portions_[idx].start, subst_.portions_[idx].len);
ptr += subst_.portions_[idx].len;
}
}
return ptr;
}
};
// CTAD deducing rule for e_subst
template<typename K, size_t N, typename...Args> requires (sizeof...(Args) > 0)
e_subst(const K(&)[N], Args&&...) -> e_subst<K, N, Args...>;
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение, которое подставляет в заданные места в строке-образце, задаваемой в рантайме, значения переданных строковых выражений.
* @en @brief String expression that substitutes the values of the passed string expressions into the specified positions in the pattern string, specified at runtime.
*/
template<typename K, typename ... Args>
struct e_vsubst : expr_to_std_string<e_vsubst<K, Args...>> {
inline static constexpr size_t Nparams = sizeof...(Args);
using symb_type = K;
using store_t = std::tuple<to_str_exp_t<K, Args>...>;
details::portion portions_[Nparams * 3];
std::vector<details::portion> more_portions_;
store_t args_;
str_src<K> pattern_;
size_t all_len_{};
unsigned actual_{};
/*!
* @ru @brief Создает строковое выражение, которое подставляет в заданные места в строке-образце, задаваемой в рантайме, значения переданных строковых выражений.
* @param pattern - Строковый объект-образец, в который будут подставляться значения переданных аргументов.
* @param args... - аргументы, которые будут подставляться в заданные места образца. Также, как и в `e_concat`, могут быть строковые литералы,
* строковые выражения, стандартные строки, а также любые типы, для которых есть преобразование в строковое выражение.
* @details Функция создаёт строковое выражение, которое при материализации генерирует текст из образца, подставляя в места подстановки
* значения переданных аргументов. Строка-образец задается в рантайм, строковым объектом, парсинг которого происходит во время выполнения.
* Места вставки обозначаются либо как `{}`, либо как `{номер}`.
* В случае без указания номера, параметры подставляются в переданном в функцию порядке. В случае указания номера, параметры подставляются в соответствии
* с указанным порядковым номером. Нумерация параметров начинается с 1. Смешивать параметры без номера и с номером нельзя - используется только
* один из вариантов для всех подстановок. В случае указания номеров - один параметр может участвовать в нескольких подстановках.
* Все переданные параметры должны участвовать в подстановках. В случае ошибки парсинга строки будет выкинуто исключение `parse_subst_string_error`.
* Для вставки самих фигурных скобок они должны удваиваться - `{{`, `}}`.
* Функция не является заменой `std::vformat`, не работает с образцом, задаваемым в compile-time, и не поддерживает каких-либо параметров форматирования
* подставляемых значений. Все передаваемые аргументы должны сами уметь преобразовывать себя в строковые выражения
* (см. @ref ConvertToStrExpr "Конвертация типов в в строковые выражения").
* @en @brief Creates a string expression that substitutes the values of the passed string expressions into the specified positions in the pattern string, specified at runtime.
* @param pattern - the pattern string object into which the values of the passed arguments will be substituted.
* @param args... - the arguments to be substituted into the specified positions in the pattern. As in `e_concat`, these can be string literals,
* string expressions, standard strings, and any types that can be converted to string expressions.
* @details The function creates a string expression that, when materialized, generates text from the pattern, substituting the values of the passed arguments into the
* substitution positions. The pattern string is specified at runtime by a string object that is parsed at runtime.
* Insertion points are designated either as `{}` or `{number}`.
* If no number is specified, parameters are substituted in the order passed to the function. If a number is specified, parameters are substituted according to the
* specified ordinal number. Parameter numbering starts with 1. You cannot mix parameters with and without numbers; only one of the options is used for all substitutions. If numbers are specified, one parameter can participate in multiple substitutions.
* All passed parameters must participate in substitutions. If a string parsing error occurs, a `parse_subst_string_error` exception will be thrown.
* To insert the curly braces themselves, they must be doubled - `{{`, `}}`.
* This function is not a replacement for `std::vformat`, does not work with the pattern specified at compile-time, and does not support any formatting options for
* substituted values. All passed arguments must be able to convert themselves to string expressions.
* (See @ref ConvertToStrExpr "Converting Types to String Expressions").
* @ru Пример: @en Example: @~
* ```cpp
* std::string_view pattern;
* .....
* lstringa<100> text = e_vsubst(pattern, from, total, success ? "success"_ss : "fail"_ss);
* ```
*/
constexpr e_vsubst(str_src<K> pattern, Args&&...args)
: pattern_(pattern)
, args_(to_subst<K>(std::forward<Args>(args))...) {
all_len_ = details::parse_pattern_string<K, Nparams>(pattern_, [this](unsigned from, unsigned len) {
if (actual_ < std::size(portions_)) {
portions_[actual_++].set_part(from, len);
} else {
if (actual_ == std::size(portions_)) {
more_portions_.reserve((pattern_.len - 1) * 2 / 3 - std::size(portions_));
}
more_portions_.emplace_back().set_part(from, len);
}
}, [this](unsigned param) {
if (actual_ < std::size(portions_)) {
portions_[actual_++].set_param(param);
} else {
if (actual_ == std::size(portions_)) {
more_portions_.reserve((pattern_.len - 1) * 2 / 3 - std::size(portions_));
}
more_portions_.emplace_back().set_param(param);
}
}
);
}
constexpr size_t length() const noexcept {
return [this]<size_t...Indexes>(std::index_sequence<Indexes...>) {
size_t idx = 0;
size_t expr_length_[Nparams] = {};
((expr_length_[idx++] = std::get<Indexes>(args_).length()),...);
size_t l = all_len_;
for (idx = 0; idx < actual_; idx++) {
if (portions_[idx].is_param) {
l += expr_length_[portions_[idx].start];
}
}
for (const auto& p : more_portions_) {
if (p.is_param) {
l += expr_length_[p.start];
}
}
return l;
}(std::make_index_sequence<sizeof...(Args)>());
}
template<size_t Idx>
constexpr K* place_idx(K* ptr, size_t idx) const noexcept {
if (idx == Idx) {
return (K*)std::get<Idx>(args_).place((typename std::remove_cvref_t<std::tuple_element_t<Idx, store_t>>::symb_type*)ptr);
}
if constexpr (Idx < Nparams - 1) {
return place_idx<Idx + 1>(ptr, idx);
}
return ptr;
}
constexpr K* place(K* ptr) const noexcept {
for (size_t idx = 0; idx < actual_; idx++) {
if (portions_[idx].is_param) {
ptr = place_idx<0>(ptr, portions_[idx].start);
} else {
ch_traits<K>::copy(ptr, pattern_.symbols() + portions_[idx].start, portions_[idx].len);
ptr += portions_[idx].len;
}
}
for (const auto& p : more_portions_) {
if (p.is_param) {
ptr = place_idx<0>(ptr, p.start);
} else {
const K* from = pattern_.symbols() + p.start;
for (size_t idx = p.len; idx--;) {
*ptr++ = *from++;
}
//ch_traits<K>::copy(ptr, pattern_.symbols() + p.start, p.len);
//ptr += p.len;
}
}
return ptr;
}
};
// CTAD deducing rule for e_vsubst
template<StrSourceNoLiteral T, typename...Args> requires (sizeof...(Args) > 0)
e_vsubst(T&&, Args&&...) -> e_vsubst<symb_type_from_src_t<T>, Args...>;
/*!
* @ru @brief Небольшое пространство для методов работы со стандартными строками.
* @en @brief Small namespace for standard string methods.
*/
namespace str {
/*!
* @ru @brief Изменить часть стандартной строки на заданное строковое выражение.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param from - начальная позиция замены.
* @param count - количество символов, подлежащих замене.
* @param expr - строковое выражение для замены.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, при необходимости увеличивает размер строки,
* и материализует строковое выражение на нужное место, без использования промежуточных буферов.
* При увеличении размера строки - до C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief Replace a portion of a standard string with the given string expression.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param from - starting position of replacement.
* @param count - number of characters to replace.
* @param expr - string expression to replace.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the passed string.
* @details The method gets the length of the string expression, increases the string size if necessary,
* and materializes the string expression at the desired location, without using intermediate buffers.
* When increasing the string size, resize is used before C++23; resize_and_overwrite is used starting with C++23.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& change(std::basic_string<K, std::char_traits<K>, A>& str, size_t from, size_t count, const E& expr) {
size_t expr_length = expr.length();
if (!expr_length) {
str.erase(from, count);
return str;
}
size_t str_length = str.length();
if (from > str_length) {
from = str_length;
}
if (from + count > str_length) {
count = str_length - from;
}
size_t new_length = str_length - count + expr_length;
size_t tail_length = str_length - count - from;
if (new_length <= str_length) {
K* data = str.data();
expr.place((typename E::symb_type*)data + from);
if (expr_length < count) {
if (tail_length) {
std::char_traits<K>::move(data + from + expr_length, data + from + count, tail_length);
}
str.resize(new_length);
}
} else {
auto fill = [&](K* data, size_t) -> size_t {
if (tail_length) {
std::char_traits<K>::move(data + from + expr_length, data + from + count, tail_length);
}
expr.place((typename E::symb_type*)data + from);
return new_length;
};
if constexpr (requires{str.resize_and_overwrite(new_length, fill);}) {
str.resize_and_overwrite(new_length, fill);
} else if constexpr (requires{str._Resize_and_overwrite(new_length, fill);}) {
str._Resize_and_overwrite(new_length, fill);
} else {
str.resize(new_length);
fill(str.data(), 0);
}
}
return str;
}
/*!
* @ru @brief Добавить к стандартной строке строковое выражение.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param expr - строковое выражение для вставки.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, увеличивает размер строки,
* и материализует строковое выражение за концом строки, без использования промежуточных буферов.
* До C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief Append a string expression to a standard string.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the passed string.
* @details The method gets the length of the string expression, increases the size of the string,
* and materializes the string expression beyond the end of the string, without using intermediate buffers.
* Before C++23, resize was used; since C++23, resize_and_overwrite was used.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& append(std::basic_string<K, std::char_traits<K>, A>& str, const E& expr) {
return change(str, str.length(), 0, expr);
}
/*!
* @ru @brief Вставить строковое выражение в начало стандартной строки.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param expr - строковое выражение для вставки.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, увеличивает размер строки, сдвигает её содержимое
* и материализует строковое выражение в её начало, без использования промежуточных буферов.
* До C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief Insert a string expression at the beginning of a standard string.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the passed string.
* @details The method gets the length of the string expression, increases the size of the string, shifts its contents
* and materializes the string expression at the beginning of the string, without using intermediate buffers.
* Before C++23, resize was used; since C++23, resize_and_overwrite was used.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& prepend(std::basic_string<K, std::char_traits<K>, A>& str, const E& expr) {
return change(str, 0, 0, expr);
}
/*!
* @ru @brief Вставить строковое выражение в указанную позицию стандартной строки.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param from - позиция вставки.
* @param expr - строковое выражение для вставки.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, увеличивает размер строки, сдвигает её содержимое
* и материализует строковое выражение на нужное место, без использования промежуточных буферов.
* До C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief Insert a string expression at the specified position in a standard string.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param from - insertion position.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - a reference to the passed string.
* @details The method gets the length of the string expression, increases the size of the string, shifts its contents
* and materializes the string expression to the desired location, without using intermediate buffers.
* Before C++23, resize was used; since C++23, resize_and_overwrite was used.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& insert(std::basic_string<K, std::char_traits<K>, A>& str, size_t from, const E& expr) {
return change(str, from, 0, expr);
}
/*!
* @ru @brief Переписать всю строку заданным строковым выражением, при необходимости изменив размер строки.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param expr - строковое выражение для вставки.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод заменяет содержимое строки результатом строкового выражения.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief Rewrite the entire string with the given string expression, resizing the string if necessary.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - a reference to the passed string.
* @details The method replaces the contents of a string with the result of a string expression.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& overwrite(std::basic_string<K, std::char_traits<K>, A>& str, const E& expr) {
if (size_t expr_length = expr.length()) {
if (expr_length <= str.length()) {
K* data = str.data();
expr.place((typename E::symb_type*)data);
str.resize(expr_length);
} else {
auto fill = [&](K* data, size_t) -> size_t {
expr.place((typename E::symb_type*)data);
return expr_length;
};
if constexpr (requires{str.resize_and_overwrite(expr_length, fill);}) {
str.resize_and_overwrite(expr_length, fill);
} else if constexpr (requires{str._Resize_and_overwrite(expr_length, fill);}) {
str._Resize_and_overwrite(expr_length, fill);
} else {
str.resize(expr_length);
expr.place((typename E::symb_type*)str.data());
}
}
} else {
str.clear();
}
return str;
}
namespace details {
template<typename K, typename A, typename E>
struct replace_grow_helper {
using my_type = std::basic_string<K, std::char_traits<K>, A>;
replace_grow_helper(my_type& src, str_src<K> p, const K* r, size_t rl, size_t mc, size_t d, const E& e)
: str(src), source(src), pattern(p), repl(const_cast<K*>(r)), replLen(rl), maxCount(mc), delta(d), expr(e) {}
my_type& str;
const str_src<K> source;
const str_src<K> pattern;
K* repl;
const size_t replLen;
size_t maxCount;
const size_t delta;
size_t all_delta{};
const E& expr;
K* reserve_for_copy{};
size_t end_of_piece{};
size_t total_length{};
std::optional<my_type> dst;
void replace(size_t offset) {
size_t found[16] = {offset};
maxCount--;
offset += pattern.len;
all_delta += delta;
size_t idx = 1;
for (; idx < std::size(found) && maxCount > 0; idx++, maxCount--) {
found[idx] = source.find(pattern, offset);
if (found[idx] == npos) {
break;
}
offset = found[idx] + pattern.len;
all_delta += delta;
}
if (idx == std::size(found) && maxCount > 0 && (offset = source.find(pattern, offset)) != str::npos) {
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;
my_type* dst_str{};
if (total_length <= str.capacity()) {
// Строка поместится в старое место | The line will be placed in the old location.
dst_str = &str;
} else {
// Будем создавать в другом буфере | We will create in another buffer.
dst_str = &dst.emplace();
}
auto fill = [this](K* p, size_t) -> size_t {
reserve_for_copy = p;
return total_length;
};
if constexpr (requires{dst_str->_Resize_and_overwrite(total_length, fill);}) {
dst_str->_Resize_and_overwrite(total_length, fill);
} else if constexpr (requires{dst_str->resize_and_overwrite(total_length, fill);}) {
dst_str->resize_and_overwrite(total_length, fill);
} else {
dst_str->resize(total_length);
reserve_for_copy = dst_str->data();
}
}
const K* src_start = str.c_str();
while(idx-- > 0) {
size_t pos = found[idx] + pattern.len;
size_t lenOfPiece = end_of_piece - pos;
ch_traits<K>::move(reserve_for_copy + pos + all_delta, src_start + pos, lenOfPiece);
if constexpr (std::is_same_v<E, int>) {
ch_traits<K>::copy(reserve_for_copy + pos + all_delta - replLen, repl, replLen);
} else {
if (!repl) {
repl = reserve_for_copy + pos + all_delta - replLen;
expr.place(repl);
} else {
ch_traits<K>::copy(reserve_for_copy + pos + all_delta - replLen, repl, replLen);
}
}
all_delta -= delta;
end_of_piece = found[idx];
}
if (!all_delta && reserve_for_copy != src_start) {
ch_traits<K>::copy(reserve_for_copy, src_start, found[0]);
str = std::move(*dst);
}
}
};
} // namespace details
/*!
* @ru @brief Функция поиска подстрок в стандартной строке и замены найденных вхождений на значение строкового выражения.
* @tparam K - тип символов строки.
* @tparam A - тип аллокатора строки.
* @tparam E - тип строкового выражения.
* @tparam T - тип подстроки поиска.
* @param str - строка, в которой заменяем вхождения подстрок.
* @param pattern - искомая подстрока (любой тип, конвертирующийся в simple_str).
* @param repl - строковое выражение для замены.
* @param offset - начальное смещение для поиска.
* @param max_count - максимальное количество замен.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на модифицируемую строку.
* @details Части строкового выражения не должны ссылаться на саму модифицируемую строку.
* Если искомая подстрока не найдена, то строковое выражение даже не вычисляется.
* Затем при осуществлении замены строковое выражение вычисляется только один раз в место первой замены,
* а в следующие места замен просто копируется символы из первого места. Это позволяет экономить память
* и время, если вам надо сделать замену на какую-либо "сборную" строку.
* @en @brief A function for searching for substrings in a standard string and replacing the found occurrences with a string expression.
* @tparam K - the string character type.
* @tparam A - the string allocator type.
* @tparam E - the string expression type.
* @tparam T - the search substring type.
* @param str - the string in which to replace substring occurrences.
* @param pattern - the search substring (any type convertible to simple_str).
* @param repl - the string expression to replace.
* @param offset - the starting offset for the search.
* @param max_count - the maximum number of replacements.
* @return std::basic_string<K, std::char_traits<K>, A>& - a reference to the string being modified.
* @details Parts of the string expression must not reference the string being modified itself.
* If the search substring is not found, the string expression is not even evaluated.
* Then, when performing a replacement, the string expression is evaluated only once at the first replacement location,
* and characters from the first location are simply copied to subsequent replacement locations. This saves memory
* and time if you need to replace with some kind of "composite" string.
*/
template<typename K, typename A, StrExprForType<K> E, typename T>
requires (std::is_constructible_v<str_src<K>, T>)
std::basic_string<K, std::char_traits<K>, A>& replace(std::basic_string<K, std::char_traits<K>, A>& str, T&& pattern, const E& repl, size_t offset = 0, size_t max_count = -1) {
if (!max_count) {
return str;
}
str_src<K> src = str;
str_src<K> spattern{std::forward<T>(pattern)};
offset = src.find(pattern, offset);
if (offset == npos) {
return str;
}
size_t replLen = repl.length();
K* replStart{};
if (spattern.len == replLen) {
// Заменяем inplace на подстроку такой же длины
// Replace inplace with a substring of the same length
K* ptr = str.data();
replStart = ptr + offset;
repl.place(replStart);
while (--max_count) {
offset = src.find(spattern, offset + replLen);
if (offset == npos)
break;
ch_traits<K>::copy(ptr + offset, replStart, replLen);
}
} else if (spattern.len > replLen) {
// Заменяем на более короткий кусок, длина текста уменьшится, идём слева направо
// Replace with a shorter piece, the length of the text will decrease, go from left to right
K* ptr = str.data();
replStart = ptr + offset;
repl.place(replStart);
size_t posWrite = offset + replLen;
offset += spattern.len;
while (--max_count) {
size_t idx = src.find(spattern, offset);
if (idx == npos)
break;
size_t lenOfPiece = idx - offset;
ch_traits<K>::move(ptr + posWrite, ptr + offset, lenOfPiece);
posWrite += lenOfPiece;
ch_traits<K>::copy(ptr + posWrite, replStart, replLen);
posWrite += replLen;
offset = idx + spattern.len;
}
size_t tailLen = src.len - offset;
ch_traits<K>::move(ptr + posWrite, ptr + offset, tailLen);
str.resize(posWrite + tailLen);
} else {
details::replace_grow_helper<K, A, E>(str, spattern, nullptr, replLen, max_count, replLen - spattern.len, repl).replace(offset);
}
return str;
}
/*!
* @ru @brief Функция поиска подстрок в стандартной строке и замены найденных вхождений на другую подстроку.
* @tparam K - тип символов строки.
* @tparam A - тип аллокатора строки.
* @param str - строка, в которой заменяем вхождения подстрок.
* @param pattern - искомая подстрока (любой тип, конвертирующийся в simple_str).
* @param repl - строка для замены (любой тип, конвертирующийся в simple_str).
* @param offset - начальное смещение для поиска.
* @param max_count - максимальное количество замен.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на модифицируемую строку.
* @en @brief Function for searching for substrings in a standard string and replacing found occurrences with another substring.
* @tparam K - the character type of the string.
* @tparam A - the type of the string allocator.
* @param str - the string in which we replace occurrences of substrings.
* @param pattern - the searched substring (any type that converts to simple_str).
* @param repl - replacement string (any type convertible to simple_str).
* @param offset - the starting offset for the search.
* @param max_count - maximum number of replacements.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the modified string.
*/
template<typename K, typename A>
std::basic_string<K, std::char_traits<K>, A>& replace(std::basic_string<K, std::char_traits<K>, A>& str, str_src<K> pattern, str_src<K> repl, size_t offset = 0, size_t max_count = -1) {
if (!max_count) {
return str;
}
str_src<K> src = str;
offset = src.find(pattern, offset);
if (offset == npos) {
return str;
}
if (pattern.len == repl.len) {
// Заменяем inplace на подстроку такой же длины
// Replace inplace with a substring of the same length
K* ptr = str.data();
while (max_count--) {
ch_traits<K>::copy(ptr + offset, repl.str, repl.len);
offset = src.find(pattern, offset + repl.len);
if (offset == npos)
break;
}
} else if (pattern.len > repl.len) {
// Заменяем на более короткий кусок, длина текста уменьшится, идём слева направо
// Replace with a shorter piece, the length of the text will decrease, go from left to right
K* ptr = str.data();
ch_traits<K>::copy(ptr + offset, repl.str, repl.len);
size_t posWrite = offset + repl.len;
offset += pattern.len;
while (--max_count) {
size_t idx = src.find(pattern, offset);
if (idx == npos)
break;
size_t lenOfPiece = idx - offset;
ch_traits<K>::move(ptr + posWrite, ptr + offset, lenOfPiece);
posWrite += lenOfPiece;
ch_traits<K>::copy(ptr + posWrite, repl.str, repl.len);
posWrite += repl.len;
offset = idx + pattern.len;
}
size_t tailLen = src.len - offset;
ch_traits<K>::move(ptr + posWrite, ptr + offset, tailLen);
str.resize(posWrite + tailLen);
} else {
details::replace_grow_helper<K, A, int>(str, pattern, repl.str, repl.len, max_count, repl.len - pattern.len, 0).replace(offset);
}
return str;
}
template<typename K, typename A, typename T, typename M>
requires (std::is_constructible_v<str_src<K>, T> && std::is_constructible_v<str_src<K>, M>)
std::basic_string<K, std::char_traits<K>, A>& replace(std::basic_string<K, std::char_traits<K>, A>& str, T&& pattern, M&& repl, size_t offset = 0, size_t max_count = -1) {
return replace(str, str_src<K>{std::forward<T>(pattern)}, str_src<K>{std::forward<M>(repl)}, offset, max_count);
}
} // namespace str
} // namespace simstr
namespace std {
/*!
* @ingroup StrExprs
* @ru @brief Унарный оператор+ для преобразования стандартных строк в строковые выражения.
* @details Стандартные строки могут напрямую участвовать в строковых выражениях только когда
* другой операнд тоже является строковым выражением. Если другой операнд - не строковое выражение,
* используйте этот оператор, чтобы превратить `std::basic_string` или `std::basic_string_view`
* в строковое выражение.
* @en @brief Unary operator + for converting standard strings to string expressions.
* @details Standard strings can only participate directly in string expressions when
* the other operand is also a string expression. If the other operand is not a string expression,
* use this operator to turn `std::basic_string` or `std::basic_string_view`
* into string expression.
* @ru Пример: @en Example @~
* ```cpp
* std::string make_text(const std::string& text, int count, std::string_view what, std::string_view what_p = ""sv) {
* return +text + " " + count + " " + e_choice(what_p.empty(), what + e_if(count > 1, "s"), e_choice(count > 1, +what_p, +what));
* }
* ```
*/
template<simstr::StdStrSource T>
simstr::expr_stdstr<typename T::value_type, T> operator+(const T& str) {
return {str};
}
/*!
* @brief Оператор для добавления строкового выражения к стандартной строке.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param expr - строковое выражение для добавления.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, увеличивает размер строки,
* и материализует строковое выражение за концом строки, без использования промежуточных буферов.
* До C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief An operator for appending a string expression to a standard string.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the passed string.
* @details The method gets the length of the string expression, increases the size of the string,
* and materializes the string expression beyond the end of the string, without using intermediate buffers.
* Before C++23, resize was used; since C++23, resize_and_overwrite was used.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, simstr::StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& operator |=(std::basic_string<K, std::char_traits<K>, A>& str, const E& expr) {
return simstr::str::change(str, str.length(), 0, expr);
}
/*!
* @ru @brief Оператор для вставки строкового выражения в начало стандартной строки.
* @tparam K - тип символов.
* @tparam A - тип аллокатора.
* @tparam E - тип строкового выражения.
* @param str - строка.
* @param expr - строковое выражение для вставки.
* @return std::basic_string<K, std::char_traits<K>, A>& - ссылку на переданную строку.
* @details Метод получает у строкового выражения его длину, увеличивает размер строки, сдвигает её содержимое
* и материализует строковое выражение в её начало, без использования промежуточных буферов.
* До C++23 используется resize, начиная с C++23 - resize_and_overwrite.
*
* ВАЖНО!!! части строкового выражения не должны ссылаться на саму строку, иначе результат неопределён!!!
*
* @en @brief An operator for inserting a string expression at the beginning of a standard string.
* @tparam K - character type.
* @tparam A - allocator type.
* @tparam E - string expression type.
* @param str - string.
* @param expr - string expression to insert.
* @return std::basic_string<K, std::char_traits<K>, A>& - reference to the passed string.
* @details The method gets the length of the string expression, increases the size of the string, shifts its contents
* and materializes the string expression at the beginning of the string, without using intermediate buffers.
* Before C++23, resize was used; since C++23, resize_and_overwrite was used.
*
* IMPORTANT!!! Parts of a string expression must not reference the string itself, otherwise the result is undefined!!!
*
*/
template<typename K, typename A, simstr::StrExprForType<K> E>
std::basic_string<K, std::char_traits<K>, A>& operator ^=(std::basic_string<K, std::char_traits<K>, A>& str, const E& expr) {
return simstr::str::change(str, 0, 0, expr);
}
} // namespace std