diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d65535..922f48e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include(FetchContent) project( simstr - VERSION 1.5.0 + VERSION 1.6.0 DESCRIPTION "Yet another modern C++ string library" HOMEPAGE_URL "https://github.com/orefkov/simstr" LANGUAGES CXX diff --git a/bench/bench_str.cpp b/bench/bench_str.cpp index f0ecc42..1453f75 100644 --- a/bench/bench_str.cpp +++ b/bench/bench_str.cpp @@ -52,10 +52,22 @@ void ConcatSimToSim(benchmark::State& state) { } } +void ConcatSimToSimConcat(benchmark::State& state) { + stra s1 = "start "; + for (auto _: state) { + for (int i = 1; i <= 100'000; i *= 10) { + benchmark::DoNotOptimize(s1); + stringa str = e_concat("", s1, i, " end"); + benchmark::DoNotOptimize(str); + } + } +} + BENCHMARK(__)->Name("----- Concatenate string + Number + \"Literal\" ---------")->Repetitions(1); -BENCHMARK(ConcatStdToStd) ->Name("Concat std::string and number by std to std::string"); -BENCHMARK(ConcatSimToStd) ->Name("Concat std::string and number by StrExpr to std::string"); -BENCHMARK(ConcatSimToSim) ->Name("Concat stringa and number by StrExpr to simstr::stringa"); +BENCHMARK(ConcatStdToStd) ->Name("Concat std::string and number by std to std::string"); +BENCHMARK(ConcatSimToStd) ->Name("Concat std::string and number by StrExpr to std::string"); +BENCHMARK(ConcatSimToSim) ->Name("Concat stringa and number by StrExpr to simstr::stringa"); +BENCHMARK(ConcatSimToSimConcat) ->Name("Concat stringa and number by e_concat to simstr::stringa"); void ConcatStdToStdHex(benchmark::State& state) { // We use a short string so that the longest result is 15 characters and fits in the std::string SSO buffer. @@ -64,7 +76,7 @@ void ConcatStdToStdHex(benchmark::State& state) { for (unsigned i = 1; i <= 100'000; i *= 10) { benchmark::DoNotOptimize(s1); // What is standard method to get hex number? - std::string str = s1 + std::format("0x{:x}", i) + " end"; + std::string str = std::format("{}0x{:x} end", s1, i); benchmark::DoNotOptimize(str); } } @@ -93,10 +105,36 @@ void ConcatSimToSimHex(benchmark::State& state) { } } +void ConcatSimToSimHexC(benchmark::State& state) { + // stringa SSO buffer is 23, but we use a short string to compare under the same conditions + stra s1 = "art "; + for (auto _: state) { + for (unsigned i = 1; i <= 100'000; i *= 10) { + benchmark::DoNotOptimize(s1); + stringa str = e_concat("", s1, e_hex(i), " end"); + benchmark::DoNotOptimize(str); + } + } +} + +void ConcatSimToSimHexS(benchmark::State& state) { + // stringa SSO buffer is 23, but we use a short string to compare under the same conditions + stra s1 = "art "; + for (auto _: state) { + for (unsigned i = 1; i <= 100'000; i *= 10) { + benchmark::DoNotOptimize(s1); + stringa str = e_subst(S_FRM("{}{} end"), s1, e_hex(i)); + benchmark::DoNotOptimize(str); + } + } +} + BENCHMARK(__)->Name("----- Concatenate string + Hex Number + \"Literal\" ---------")->Repetitions(1); BENCHMARK(ConcatStdToStdHex) ->Name("Concat std::string and hex number by std to std::string"); BENCHMARK(ConcatSimToStdHex) ->Name("Concat std::string and hex number by StrExpr to std::string"); BENCHMARK(ConcatSimToSimHex) ->Name("Concat stringa and hex number by StrExpr to simstr::stringa"); +BENCHMARK(ConcatSimToSimHexC) ->Name("Concat stringa and hex number by e_concat to simstr::stringa"); +BENCHMARK(ConcatSimToSimHexS) ->Name("Concat stringa and hex number by e_subst to simstr::stringa"); void ConcatStdToStdS(benchmark::State& state) { std::string s1 = "start "; @@ -205,7 +243,6 @@ BENCHMARK(FindConcatThreeStr)->Name("Find concat three std::string"); BENCHMARK(FindConcatThreeExp)->Name("Find concat three strexpr"); BENCHMARK(FindConcatThreeSim)->Name("Find concat three simstr"); - std::string buildTypeNameStr(std::string_view type_name, size_t prec, size_t scale) { std::string res{type_name}; if (prec) { diff --git a/docs/Doxyfile b/docs/Doxyfile index 96c6834..14de555 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = "simstr" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.5.0 +PROJECT_NUMBER = 1.6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a @@ -992,7 +992,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../src ../include +INPUT = ../src ../include ../tests # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses @@ -1127,7 +1127,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = ../tests # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/docs/Doxyfile_ru b/docs/Doxyfile_ru index e88e5db..36c3488 100644 --- a/docs/Doxyfile_ru +++ b/docs/Doxyfile_ru @@ -48,7 +48,7 @@ PROJECT_NAME = "simstr" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.5.0 +PROJECT_NUMBER = 1.6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a @@ -992,7 +992,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../src ../include +INPUT = ../src ../include ../tests # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses @@ -1127,7 +1127,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = ../tests # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/include/simstr/simple_unicode.h b/include/simstr/simple_unicode.h index d7328de..f5789bd 100644 --- a/include/simstr/simple_unicode.h +++ b/include/simstr/simple_unicode.h @@ -1,6 +1,6 @@ /* * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com - * ver. 1.5.0 + * ver. 1.6.0 */ #pragma once diff --git a/include/simstr/sstring.h b/include/simstr/sstring.h index a06e3b9..af7f3eb 100644 --- a/include/simstr/sstring.h +++ b/include/simstr/sstring.h @@ -1,9 +1,9 @@ /* * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com - * ver. 1.5.0 + * ver. 1.6.0 * Классы для работы со строками * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com -* ver. 1.5.0 +* ver. 1.6.0 * Classes for working with strings */ @@ -48,7 +48,6 @@ const bool isWindowsOs = // NOLINT #define IN_FULL_SIMSTR #include "strexpr.h" -#undef simple_str #include #include @@ -3374,9 +3373,6 @@ protected: const K* sstr_; }; size_t bigLen_; // Длина не локальной строки | Non-local string length - uns_type pad_[LocalCount - (sizeof(const K*) + sizeof(size_t)) / sizeof(K)]; - uns_type blocalRemain_ : sizeof(uns_type) * CHAR_BIT - 2; - uns_type btype_ : 2; }; }; @@ -3385,7 +3381,7 @@ protected: localRemain_ = LocalCount; buf_[0] = 0; } - K* init(size_t s) { + constexpr K* init(size_t s) { if (s > LocalCount) { type_ = Shared; localRemain_ = 0; @@ -3472,7 +3468,7 @@ public: */ template requires std::is_constructible_v - sstring(s_str other, Args&&... args) : base_storable(std::forward(args)...), buf_{0} { + sstring(s_str other, Args&&... args) : base_storable(std::forward(args)...) { base_storable::init_from_str_other(other); } /*! @@ -3550,7 +3546,7 @@ public: static const sstring empty_str; /// @ru Деструктор строки. @en String destructor. constexpr ~sstring() { - if (btype_ == Shared) { + if (type_ == Shared) { SharedStringData::from_str(sstr_)->decr(base_storable::allocator()); } } @@ -3635,13 +3631,11 @@ public: */ template::Count, typename... Args> requires std::is_constructible_v - constexpr sstring(T&& s, Args&&... args) : base_storable(std::forward(args)...) - , btype_(Constant) - , blocalRemain_(0) - , cstr_(s) - , bigLen_(N - 1) - , pad_{} - { + sstring(T&& s, Args&&... args) : base_storable(std::forward(args)...) { + type_ = Constant; + localRemain_ = 0; + cstr_ = s; + bigLen_ = N - 1; } constexpr void swap(my_type&& other) noexcept { @@ -3738,11 +3732,11 @@ public: } /// @ru Указатель на символы строки. @en Pointer to characters in the string. constexpr const K* symbols() const noexcept { - return btype_ == Local ? buf_ : cstr_; + return type_ == Local ? buf_ : cstr_; } /// @ru Длина строки. @en Line length. constexpr size_t length() const noexcept { - return btype_ == Local ? LocalCount - blocalRemain_ : bigLen_; + return type_ == Local ? LocalCount - localRemain_ : bigLen_; } /// @ru Пустая ли строка. @en Is the string empty? constexpr bool is_empty() const noexcept { @@ -3802,9 +3796,11 @@ template inline const sstring sstring::empty_str{}; template -consteval simple_str_nt select_str(simple_str_nt s8, simple_str_nt sw, simple_str_nt s16, simple_str_nt s32) { +consteval simple_str_nt select_str(simple_str_nt s8, simple_str_nt sb, simple_str_nt sw, simple_str_nt s16, simple_str_nt s32) { if constexpr (std::is_same_v) return s8; + if constexpr (std::is_same_v) + return sb; if constexpr (std::is_same_v) return sw; if constexpr (std::is_same_v) @@ -3813,7 +3809,7 @@ consteval simple_str_nt select_str(simple_str_nt s8, simple_str_nt return s32; } -#define uni_string(K, p) select_str(p, L##p, u##p, U##p) +#define uni_string(K, p) select_str(p, u8##p, L##p, u##p, U##p) template struct StoreType { diff --git a/include/simstr/strexpr.h b/include/simstr/strexpr.h index aa91382..e4554fc 100644 --- a/include/simstr/strexpr.h +++ b/include/simstr/strexpr.h @@ -1,5 +1,5 @@ /* - * ver. 1.5.0 + * ver. 1.6.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com * База для строковых конкатенаций через выражения времени компиляции * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com @@ -89,10 +89,10 @@ template struct is_one_of_type : std::false_type {}; template -constexpr bool is_one_of_char_v = is_one_of_type::value; +concept is_one_of_char_v = is_one_of_type::value; template -constexpr bool is_one_of_std_char_v = is_one_of_type::value; +concept is_one_of_std_char_v = is_one_of_type::value; template requires (is_one_of_std_char_v) @@ -133,7 +133,7 @@ auto to_one_of_std_char(const From* from) { * This is for the ability to mix string expressions of compatible types. */ template -constexpr bool is_equal_str_type_v = is_one_of_char_v && is_one_of_char_v && sizeof(K1) == sizeof(K2); +concept is_equal_str_type_v = sizeof(K1) == sizeof(K2) && is_one_of_char_v && is_one_of_char_v; /* Вспомогательные шаблоны для определения строковых литералов. @@ -239,6 +239,19 @@ public: } }; +template +concept StrTypeCommon = requires(const A& a) { + typename std::remove_cvref_t::symb_type; + { a.is_empty() } -> std::same_as; + { a.length() } -> std::convertible_to; + { a.symbols() }; +}; + +template +concept HasSymbType = requires { + typename std::remove_cvref_t::symb_type; +}; + /*! * @ru @brief Базовая концепция строкового объекта. * @tparam A - проверяемый тип @@ -261,12 +274,65 @@ public: * - `typename symb_type`: sets the character type of the string */ template -concept StrType = requires(const A& a) { - { a.is_empty() } -> std::same_as; - { a.length() } -> std::convertible_to; +concept StrType = StrTypeCommon && requires(const A& a) { { a.symbols() } -> std::same_as; } && std::is_same_v::symb_type, K>; +template struct is_std_string_source : std::false_type{}; + +template +struct is_std_string_source, A>> : std::true_type{}; + +template +struct is_std_string_source>> : std::true_type{}; + +template +concept is_std_string_source_v = is_std_string_source::value; + +template +concept StdStrSource = is_std_string_source_v>; + +template +concept StdStrSourceForType = StdStrSource && is_equal_str_type_v; + +template +concept StrSource = StdStrSource || is_const_lit_v || StrTypeCommon; + +template struct is_std_string : std::false_type{}; + +template +struct is_std_string, A>> : std::true_type{}; + +template +concept is_std_string_v = is_std_string::value; +template +concept StdStringForType = is_std_string_v && is_equal_str_type_v; + +template +struct symb_type_from_src { + using type = void; + +}; + +template +struct symb_type_from_src { + using type = K; +}; + +template +struct symb_type_from_src { + using type = typename std::remove_cvref_t::value_type; +}; + +template +struct symb_type_from_src { + using type = typename std::remove_cvref_t::symb_type; +}; + +template +using symb_type_from_src_t = symb_type_from_src::type; + + /*! * @ru @defgroup StrExprs Строковые выражения * @brief Описание строковых выражений @@ -328,6 +394,8 @@ concept StrType = requires(const A& a) { * В одно выражение могут объединятся строковые выражения для символов разных, но совместимых типов. * То есть можно сочетать `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" @@ -387,6 +455,8 @@ concept StrType = requires(const A& a) { * * 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. */ /*! @@ -398,9 +468,9 @@ concept StrType = requires(const A& a) { */ template concept StrExpr = requires(const A& a) { - typename A::symb_type; + typename std::remove_cvref_t::symb_type; { a.length() } -> std::convertible_to; - { a.place(std::declval()) } -> std::same_as; + { a.place(std::declval::symb_type*>()) } -> std::same_as::symb_type*>; }; /*! @@ -415,7 +485,94 @@ concept StrExpr = requires(const A& a) { * @details Used to set restrictions on a string expression by character type. */ template -concept StrExprForType = StrExpr && is_equal_str_type_v; +concept StrExprForType = StrExpr && is_equal_str_type_v::symb_type>; + +//template +//concept StdString = StrExpr && is_equal_str_type_v::value_type>; + +template +struct convert_to_strexpr; + +template +concept strexpr_from = requires(const A& a) { + {convert_to_strexpr>::convert(a)} -> StrExprForType; +}; + +template +concept strexpr_std = requires(const A& a) { + {convert_to_strexpr>::convert(a)} -> StdStringForType; +}; + +template +concept strexpr_for = StrExprForType>::type, K>; + +template +concept to_strexpr_type = StrExprForType::strexpr, K>; + +template +concept to_strexpr_meth = requires(const A& a) { + {a.template to_strexpr()} -> StrExprForType; +}; + +template +concept to_strexpr_std = requires(const A& a) { + {a.template to_strexpr()} -> StdStringForType; +}; + +template +concept convertible_to_strexpr = strexpr_for || strexpr_from || strexpr_std || to_strexpr_type || to_strexpr_meth || to_strexpr_std; + +template T> +constexpr auto to_strexpr(T&& t) { + return convert_to_strexpr>::convert(std::forward(t)); +} + +template T> +constexpr typename convert_to_strexpr>::type to_strexpr(T&& t) { + return {std::forward(t)}; +} + +template T> +constexpr typename std::remove_cvref_t::strexpr to_strexpr(T&& t) { + return {std::forward(t)}; +} + +template T> +constexpr auto to_strexpr(T&& t) { + return t.template to_strexpr(); +} + +template requires is_equal_str_type_v +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::copy(p, (const K*)t_.data(), s); + return p + s; + } +}; + +template T> +constexpr auto to_strexpr(T&& t) { + using type = decltype(convert_to_strexpr>::convert(std::forward(t))); + return expr_stdstr_c{convert_to_strexpr>::convert(std::forward(t))}; +} + +template T> +constexpr auto to_strexpr(T&& t) { + using type = decltype(t.template to_strexpr()); + return expr_stdstr_c{t.template to_strexpr()}; +} + +template +using convert_to_strexpr_t = decltype(to_strexpr(std::declval())); /* * Шаблонные классы для создания строковых выражений из нескольких источников. @@ -569,6 +726,44 @@ struct strexprjoin_c : expr_to_std_string>{ } }; +/*! + * @ru @brief Оператор сложения строкового выражения и типов, для которых есть преобразование в строковое выражение. + * @tparam A - тип строкового выражения. + * @tparam B - тип слагаемого. + * @param a - строковое выражение. + * @param b - слагаемое. + * @return constexpr strexprjoin_c, 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, true> + */ +template B> +inline constexpr strexprjoin_c, true> operator+(const A& a, B&& b) { + return {a, to_strexpr(std::forward(b))}; +} + +/*! + * @ru @brief Оператор сложения типов, для которых есть преобразование в строковое выражение и строкового выражения. + * @tparam A - тип строкового выражения. + * @tparam B - тип слагаемого. + * @param b - слагаемое. + * @param a - строковое выражение. + * @return constexpr strexprjoin_c, 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, false> + */ +template B> +inline constexpr strexprjoin_c, false> operator+(B&& b, const A& a) { + return {a, to_strexpr(std::forward(b))}; +} + /*! * @ingroup StrExprs * @ru @brief "Пустое" строковое выражение. @@ -1330,26 +1525,12 @@ constexpr auto e_if(bool c, T&& str) { return expr_choice_two_lit{str, empty, c}; } -template struct is_std_string_source : std::false_type{}; - -template -struct is_std_string_source, A>> : std::true_type{}; - -template -struct is_std_string_source>> : std::true_type{}; - -template -constexpr bool is_std_string_source_v = is_std_string_source::value; - -template -concept StdStrSource = is_std_string_source_v>; - /*! * @ingroup StrExprs * @ru @brief Тип для использования std::basic_string и std::basic_string_view как источников в строковых выражениях. * @tparam K - тип символа. * @tparam T - тип источника. - * @en @brief A type for using std::string and std::string_view as sources in string expressions. + * @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. */ @@ -1371,24 +1552,19 @@ struct expr_stdstr { }; /*! - * @ingroup StrExprs - * @ru @brief Оператор сложения для строкового выражения и стандартных строк совместимого типа. - * @en @brief The addition operator for string expression and standard strings of compatible type. + * @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 A> -constexpr strexprjoin_c, true> operator+(const A& a, const T& s) { - return {a, s}; -} - -/*! - * @ingroup StrExprs - * @ru @brief Оператор сложения для стандартных строк и строкового выражения совместимого типа. - * @en @brief The addition operator for standard strings and string expressions of compatible type. - */ -template A> -constexpr strexprjoin_c, false> operator+(const T& s, const A& a) { - return {a, s}; -} +template +struct convert_to_strexpr { + using type = expr_stdstr; +}; namespace str { constexpr const size_t npos = static_cast(-1); //NOLINT @@ -1408,8 +1584,8 @@ template struct need_sign { // NOLINT bool negate; std::make_unsigned_t val; - need_sign(T t) : negate(t < 0), val(t < 0 ? std::make_unsigned_t{} - t : t) {} - void after(K*& ptr) { + constexpr need_sign(T t) : negate(t < 0), val(t < 0 ? std::make_unsigned_t{} - t : t) {} + constexpr void after(K*& ptr) { if (negate) *--ptr = '-'; } @@ -1418,8 +1594,8 @@ struct need_sign { // NOLINT template struct need_sign { T val; - need_sign(T t) : val(t){} - void after(K*&) {} + constexpr need_sign(T t) : val(t){} + constexpr void after(K*&) {} }; template @@ -1465,11 +1641,11 @@ struct expr_num : expr_to_std_string> { constexpr expr_num(T t) : value(t) {} constexpr expr_num(expr_num&& t) : value(t.value) {} - size_t length() const noexcept { + constexpr size_t length() const noexcept { value = (T)fromInt(buf + bufSize, value); return (size_t)value; } - K* place(K* ptr) const noexcept { + constexpr K* place(K* ptr) const noexcept { size_t len = (size_t)value; ch_traits::copy(ptr, buf + bufSize - len, len); return ptr + len; @@ -1477,36 +1653,19 @@ struct expr_num : expr_to_std_string> { }; /*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для строкового выражения и целого числа. - * @param a - строковое выражение. - * @param s - число. - * @details Число конвертируется в десятичное строковое представление. - * @en @brief Concatenation operator for string expression and integer. - * @param a is a string expression. - * @param s - number. - * @details The number is converted to a decimal string representation. + * @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 -constexpr strexprjoin_c> operator + (const A& a, T s) { - return {a, s}; -} - -/*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для целого числа и строкового выражения. - * @param s - число. - * @param a - строковое выражение. - * @details Число конвертируется в десятичное строковое представление. - * @en @brief Concatenation operator for integer and string expression. - * @param s - number. - * @param a is a string expression. - * @details The number is converted to a decimal string representation. - */ -template -constexpr strexprjoin_c, false> operator + (T s, const A& a) { - return {a, s}; -} +template +struct convert_to_strexpr { + using type = expr_num; +}; /*! * @ingroup StrExprs @@ -1555,38 +1714,19 @@ struct expr_real : expr_to_std_string> { }; /*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для строкового выражения и вещественного числа (`float`, `double`). - * @param a - строковое выражение. - * @param s - число. - * @details Число конвертируется в строковое представление через sprintf("%.16g"). - * @en @brief Concatenation operator for string expression and real number (`float`, `double`). - * @param a is a string expression. - * @param s - number. - * @details The number is converted to a string representation via sprintf("%.16g"). + * @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 - requires(std::is_same_v || std::is_same_v) -inline constexpr strexprjoin_c> operator+(const A& a, R s) { - return {a, s}; -} - -/*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для вещественного числа (`float`, `double`) и строкового выражения. - * @param s - число. - * @param a - строковое выражение. - * @details Число конвертируется в строковое представление через `sprintf("%.16g")`. - * @en @brief Concatenation operator for float (`float`, `double`) and string expression. - * @param s - number. - * @param a is a string expression. - * @details The number is converted to a string representation via `sprintf("%.16g")`. - */ -template - requires(std::is_same_v || std::is_same_v) -inline constexpr strexprjoin_c, false> operator+(R s, const A& a) { - return {a, s}; -} +template +struct convert_to_strexpr { + using type = expr_real; +}; /*! * @ingroup StrExprs @@ -1604,6 +1744,12 @@ inline constexpr expr_real e_num(double t) { return {t}; } +template +struct expr_hex_src { + explicit constexpr expr_hex_src(Val v) : v_(v){} + Val v_; +}; + template constexpr K hex_symbols[16] = {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'), @@ -1616,6 +1762,7 @@ struct expr_hex : expr_to_std_string> { mutable K buf_[sizeof(Val) * 2]; explicit constexpr expr_hex(Val v) : v_(v){} + constexpr expr_hex(const expr_hex_src& v) : v_(v.v_){} constexpr size_t length() const noexcept { K *ptr = buf_ + std::size(buf_); @@ -1665,13 +1812,6 @@ struct expr_hex : expr_to_std_string> { } }; -template -requires std::is_unsigned_v -struct expr_hex_src { - explicit constexpr expr_hex_src(Val v) : v_(v){} - Val v_; -}; - /*! * @ingroup StrExprs * @ru @brief Флаги для функции e_hex. @@ -1706,60 +1846,35 @@ constexpr auto e_hex(T v) { } /*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для строкового выражения и 16ричного представления числа из e_hex(). - * @param a - строковое выражение. - * @param b - e_hex(число). - * @en @brief Concatenation operator for a string expression and a hexadecimal representation of a number from e_hex(). - * @param a is a string expression. - * @param b is e_hex(number). + * @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 -constexpr strexprjoin_c, true> operator+(const A& a, const expr_hex_src& b) { - return {a, expr_hex{b.v_}}; -} +template +struct convert_to_strexpr> { + using type = expr_hex; +}; + +template +struct expr_pointer : expr_hex { + constexpr expr_pointer(const void* ptr) : expr_hex((uintptr_t)ptr){} +}; /*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для 16ричного представления числа из e_hex() и строкового выражения. - * @param a - e_hex(число). - * @param b - строковое выражение. - * @en @brief Concatenation operator for the hexadecimal representation of a number from e_hex() and a string expression. - * @param a - e_hex(number). - * @param b is a string expression. + * @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 -constexpr strexprjoin_c, false> operator+(const expr_hex_src& b, const A& a) { - return {a, expr_hex{b.v_}}; -} - -/*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для строкового выражения и указателя, представляет его как 0xDEADBEEF. - * @param a - строковое выражение. - * @param b - указатель. - * @en @brief Concatenation operator for a string expression and a pointer, representing it as 0xDEADBEEF. - * @param a is a string expression. - * @param b is a pointer. - */ -template -constexpr strexprjoin_c, true> operator+(const A& a, const void* b) { - return {a, (uintptr_t)b}; -} - -/*! - * @ingroup StrExprs - * @ru @brief Оператор конкатенации для указателя и строкового выражения, представляет его как 0xDEADBEEF. - * @param a - указатель. - * @param b - строковое выражение. - * @en @brief Concatenation operator for a pointer and a string expression, representing it as 0xDEADBEEF. - * @param a - pointer. - * @param b is a string expression. - */ -template -constexpr strexprjoin_c, false> operator+(const void* b, const A& a) { - return {a, (uintptr_t)b}; -} +template +struct convert_to_strexpr { + using type = expr_pointer; +}; template A, bool Left> struct expr_fill : expr_to_std_string>{ @@ -2032,7 +2147,7 @@ struct convert_result { }; struct int_convert { // NOLINT - inline static const uint8_t NUMBERS[] = { + 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, @@ -3094,7 +3209,7 @@ public: * @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::string_view{_str(), _len()}.find_first_of(std::string_view{pattern.str, pattern.len}, offset); + return std::basic_string_view{_str(), _len()}.find_first_of(std::basic_string_view{pattern.str, pattern.len}, offset); } /*! * @ru @brief Найти первое вхождение символа из заданного набора символов. @@ -3108,8 +3223,8 @@ public: */ constexpr std::pair find_first_of_idx(str_piece pattern, size_t offset = 0) const noexcept { const K* text = _str(); - size_t fnd = std::string_view{text, _len()}.find_first_of(std::string_view{pattern.str, pattern.len}, offset); - return {fnd, fnd == std::string::npos ? fnd : pattern.find(text[fnd]) }; + size_t fnd = std::basic_string_view{text, _len()}.find_first_of(std::basic_string_view{pattern.str, pattern.len}, offset); + return {fnd, fnd == std::basic_string::npos ? fnd : pattern.find(text[fnd]) }; } /*! * @ru @brief Найти первое вхождение символа не из заданного набора символов. @@ -3122,7 +3237,7 @@ public: * @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::string_view{_str(), _len()}.find_first_not_of(std::string_view{pattern.str, pattern.len}, offset); + return std::basic_string_view{_str(), _len()}.find_first_not_of(std::basic_string_view{pattern.str, pattern.len}, offset); } /*! * @ru @brief Найти последнее вхождение символа из заданного набора символов. @@ -3135,7 +3250,7 @@ public: * @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::string_view{_str(), _len()}.find_last_of(std::string_view{pattern.str, pattern.len}, offset); + return std::basic_string_view{_str(), _len()}.find_last_of(std::basic_string_view{pattern.str, pattern.len}, offset); } /*! * @ru @brief Найти последнее вхождение символа из заданного набора символов. @@ -3149,8 +3264,8 @@ public: */ constexpr std::pair find_last_of_idx(str_piece pattern, size_t offset = str::npos) const noexcept { const K* text = _str(); - size_t fnd = std::string_view{text, _len()}.find_last_of(std::string_view{pattern.str, pattern.len}, offset); - return {fnd, fnd == std::string::npos ? fnd : pattern.find(text[fnd]) }; + size_t fnd = std::basic_string_view{text, _len()}.find_last_of(std::basic_string_view{pattern.str, pattern.len}, offset); + return {fnd, fnd == std::basic_string::npos ? fnd : pattern.find(text[fnd]) }; } /*! * @ru @brief Найти последнее вхождение символа не из заданного набора символов. @@ -3163,7 +3278,7 @@ public: * @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::string_view{_str(), _len()}.find_last_not_of(std::string_view{pattern.str, pattern.len}, offset); + return std::basic_string_view{_str(), _len()}.find_last_not_of(std::basic_string_view{pattern.str, pattern.len}, offset); } /*! * @ru @brief Получить подстроку. Работает аналогично operator(), только результат выдает того же типа, к которому применён метод. @@ -3542,6 +3657,14 @@ public: 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::WIDTH; const size_t mask = ascii_mask::VALUE; size_t len = _len(); @@ -4159,6 +4282,22 @@ using strw = str_src_nt; using stru = str_src_nt; using struu = str_src_nt; +template +consteval simple_str_nt select_str(simple_str_nt s8, simple_str_nt sb, simple_str_nt sw, simple_str_nt s16, simple_str_nt s32) { + if constexpr (std::is_same_v) + return s8; + if constexpr (std::is_same_v) + return sb; + if constexpr (std::is_same_v) + return sw; + if constexpr (std::is_same_v) + return s16; + if constexpr (std::is_same_v) + return s32; +} + +#define uni_string(K, p) select_str(p, u8##p, L##p, u##p, U##p) + inline namespace literals { /*! @@ -4330,49 +4469,6 @@ constexpr inline auto trimOp(str_src pattern) { return trim_operator{pattern}; } -template -concept StrSource = StdStrSource || requires { - typename std::remove_cvref_t::symb_type; -}; - -template -struct to_src_str_base { - using symb_type = typename T::symb_type; - static str_src get(const T& s) { - return {s.symbols(), s.length()}; - } -}; - -struct to_src_str_base_none {}; - -template -struct to_src_str : std::conditional_t, to_src_str_base, to_src_str_base_none> { -}; - -template -struct to_src_str> { - using symb_type = K; - static str_src get(const std::basic_string& s) { - return {s.data(), s.length()}; - } -}; - -template -struct to_src_str> { - using symb_type = K; - static str_src get(const std::basic_string_view& s) { - return {s.data(), s.length()}; - } -}; - -template -using src_str_t = to_src_str>::symb_type; - -template -auto get_str_src_from(T&& t) { - return to_src_str>::get(std::forward(t)); -} - static constexpr size_t FIND_CACHE_SIZE = 16; template @@ -4469,10 +4565,10 @@ struct expr_replaces : expr_to_std_string> { * @param p - string literal, searched substring. * @param r - string literal, what to replace with. */ -template, typename T, size_t N = const_lit_for::Count, typename X, size_t L = const_lit_for::Count> +template, typename T, size_t N = const_lit_for::Count, typename X, size_t L = const_lit_for::Count> requires(N > 1) constexpr auto e_repl(A&& w, T&& p, X&& r) { - return expr_replaces{get_str_src_from(std::forward(w)), p, r}; + return expr_replaces{std::forward(w), p, r}; } /*! @@ -4710,12 +4806,12 @@ struct expr_replaced_e : expr_to_std_string> { * @param p - string object, searched substring, maybe runtime. * @param r - string object, replace substring, maybe runtime. */ -template, typename T, typename X> +template, typename T, typename X> requires (std::is_constructible_v, T> && std::is_constructible_v, X> && (!is_const_lit_v || !is_const_lit_v)) constexpr auto e_repl(A&& w, T&& p, X&& r) { str_src pattern{std::forward(p)}; str_src repl{std::forward(r)}; - return expr_replaced{get_str_src_from(std::forward(w)), pattern, repl}; + return expr_replaced{std::forward(w), pattern, repl}; } /*! @@ -4731,11 +4827,11 @@ constexpr auto e_repl(A&& w, T&& p, X&& r) { * @param p - string object, searched substring, maybe runtime. * @param expr - string expression, what to replace with. */ -template, typename T, StrExprForType E> +template, typename T, StrExprForType E> requires std::is_constructible_v, T> constexpr auto e_repl(A&& w, T&& p, const E& expr) { str_src pattern{std::forward(p)}; - return expr_replaced_e{get_str_src_from(std::forward(w)), pattern, expr}; + return expr_replaced_e{std::forward(w), pattern, expr}; } template @@ -4880,7 +4976,6 @@ protected: 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) { @@ -4963,10 +5058,10 @@ protected: * out += "
" + repl_html_symbols(content) + "
"; * ``` */ -template, typename ... Repl> +template, typename ... Repl> requires (sizeof...(Repl) % 2 == 0) auto e_repl_const_symbols(A&& src, Repl&& ... other) { - return expr_replace_const_symbols(get_str_src_from(std::forward
(src)), std::forward(other)...); + return expr_replace_const_symbols(std::forward(src), std::forward(other)...); } /*! @@ -5208,6 +5303,392 @@ protected: } }; +template T> +struct force_copy { + const T& t_; + force_copy(const T& t) : t_(t){} +}; + +template +struct symb_type_from_src> { + using type = K; +}; + + +template +force_copy(T&&) -> force_copy; + +template +constexpr auto to_subst(T&& t) { + return to_strexpr(std::forward(t)); +} + +template +constexpr auto to_subst(const T(&t)[N]) { + return expr_literal{t}; +} + +template T> +constexpr decltype(auto) to_subst(T&& t) { + return std::forward(t); +} + +template +constexpr T to_subst(const force_copy& t) { + return t.t_; +} + +template +constexpr T to_subst(force_copy&& t) { + return t.t_; +} + +template +constexpr T to_subst(force_copy& t) { + return t.t_; +} + +template +using to_str_exp_t = decltype(to_subst(std::declval())); + +/*! + * @ru @brief Строковое выражение для объединения более чем одного строкового выражения, с указанием разделителя. + * Создаётся при вызове функции `e_concat`. + * @tparam K - тип символов. + * @tparam G - тип разделителя. + * @tparam Args... - типы строковых выражений. + * @en @brief A string expression for concatenating more than one string expression, specifying a separator. + * Created when calling the `e_concat` function. + * @tparam K - character type. + * @tparam G - separator type. + * @tparam Args... - string expression types. */ +template +struct expr_concat : expr_to_std_string> { + using symb_type = K; + using store_t = std::tuple; + G glue_; + Arg arg_; + store_t args_; + mutable size_t glue_len_; + + constexpr expr_concat(G&& glue, Arg&& arg, Args&&...args) : glue_(std::forward(glue)), arg_(std::forward(arg)), args_(std::forward(args)...) {} + + constexpr size_t length() const noexcept { + return [this](std::index_sequence) { + glue_len_ = glue_.length(); + size_t l = arg_.length() + glue_len_ * sizeof...(Args); + ((l += std::get(args_).length()),...); + return l; + }(std::make_index_sequence()); + } + constexpr K* place(K* ptr) const noexcept { + return [this](K* ptr, std::index_sequence) { + ptr = (K*)arg_.place((typename std::remove_cvref_t::symb_type*)ptr); + const K* glueStart = ptr; + ptr = glue_.place(ptr); + ( + ( + ptr = (K*)std::get(args_).place((typename std::remove_cvref_t>::symb_type*)ptr), + glue_len_ > 0 && Indexes < sizeof...(Args) - 1 ? (ch_traits::copy(ptr, glueStart, glue_len_), ptr += glue_len_) : nullptr + ), + ...); + return ptr; + }(ptr, std::make_index_sequence()); + } +}; + +/*! + * @ingroup StrExprs + * @ru @brief Создание строкового выражения, объединяющего указанные строковые выражения, с использованием + * заданного разделителя. + * @tparam T - тип разделителя, выводится из аргумента. + * @tparam K - тип символов, выводится из типа разделителя. + * @tparam Args... - склеиваемые аргументы. + * @param glue - "клей", используемый при соединении аргументов, вставляется между ними. + * @param 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. + * @tparam T - delimiter type, deduced from the argument. + * @tparam K - character type, deduced from the separator type. + * @tparam Args... - arguments to be merged. + * @param glue - the "glue" used when connecting arguments is inserted between them. + * @param 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); + * ``` + */ +template, typename ... Args> requires (sizeof...(Args) > 1) +constexpr expr_concat, to_str_exp_t...> e_concat(T&& glue, Args&&...args) { + return { to_subst(std::forward(glue)), to_subst(std::forward(args))...}; +} + +namespace details { + +template +struct subst_params { + struct parse_subst_string_error { + parse_subst_string_error(const char*){} + }; + + 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; + } + }; + + inline static constexpr size_t NParams = sizeof...(Args); + inline static constexpr size_t PtLen = const_lit::Count; + + Pt source_; + unsigned all_len_{}; + unsigned actual_{}; + // The pattern string can be divided into a maximum of this number of portions. + // "a{}a{}a{}a" - Two portions of one symbol from the edges, and two portions for every three symbols + portion portions_[2 + PtLen / 3 * 2]{}; + + consteval subst_params(Pt&& pattern) : source_(pattern) { + const K* first = pattern; + const K* last = first + PtLen - 1; + char used_args[NParams] = {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_data = 0, idx_in_params = 0; + + while (first != last) { + bool cont = false; + 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) { + if (open_pos < last && open_pos[1] == '{') { + unsigned len = open_pos - first + 1; + portions_[idx_in_data++].set_part(first - pattern, len); + all_len_ += len; + first = open_pos + 2; + cont = true; + } else if (unsigned len = open_pos - first) { + portions_[idx_in_data++].set_part(first - pattern, 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; + portions_[idx_in_data++].set_part(first - pattern, len); + all_len_ += len; + first = ++close_pos; + } + if (open_pos == last) { + break; + } + } + if (cont) { + continue; + } + + 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"}; + } + used_args[idx_in_params]++; + portions_[idx_in_data++].set_param(idx_in_params++); + first = open_pos + 1; + } else if (*open_pos == '{') { + portions_[idx_in_data++].set_part(open_pos - pattern, 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(open_pos, end - open_pos).template to_int(); + if (err != IntConvertResult::Success || p < 1 || p > NParams) { + throw parse_subst_string_error{"bad param id"}; + } + used_args[--p]++; + portions_[idx_in_data++].set_param(p); + first = end + 1; + } + } + for (auto c : used_args) { + if (!c) { + throw parse_subst_string_error{"unused param"}; + } + } + actual_ = idx_in_data; + } +}; + +} // namespace details + +template +struct expr_subst : expr_to_std_string> { + inline static constexpr size_t Nparams = sizeof...(Args); + using symb_type = K; + using store_t = std::tuple...>; + + const details::subst_params& subst_; + store_t args_; + + constexpr expr_subst(const details::subst_params& subst, Args&&...args) + : subst_(subst) + , args_(to_subst(std::forward(args))...){} + + constexpr size_t length() const noexcept { + return [this](std::index_sequence) { + size_t idx = 0; + size_t expr_length_[Nparams] = {}; + ((expr_length_[idx++] = std::get(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()); + } + template + constexpr K* place_idx(K* ptr, size_t idx) const noexcept { + if (idx == Idx) { + return (K*)std::get(args_).place((typename std::remove_cvref_t>::symb_type*)ptr); + } + if constexpr (Idx < Nparams - 1) { + return place_idx(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::copy(ptr, subst_.source_ + subst_.portions_[idx].start, subst_.portions_[idx].len); + ptr += subst_.portions_[idx].len; + } + } + return ptr; + } +}; + +/*! + * @ingroup StrExprs + * @ru @brief Создает строковое выражение, которое подставляет в заданные места в строковом литерале - образце значения переданных строковых выражений. + * @tparam T - тип строки-образца, выводится из аргумента. + * @tparam Args... - типы переданных аргументов. + * @param str_pattern - Строка-образец, строковый литерал, служит для вывода типа символов выражения и получения длины образца во время компиляции. + * @param pattern - распарсенная во время компиляции информация составе строки-шаблона, содержит длины текстовых порций и места вставки параметров. + * @param args... - аргументы, которые будут подставляться в заданные места образца. Также, как и в `e_concat`, могут быть строковые литералы, + * строковые выражения, стандартные строки, а также любые типы, для которых есть преобразование в строковое выражение. + * @details Функция создаёт строковое выражение, которое при материализации генерирует текст из образца, подставляя в места подстановки + * значения переданных аргументов. Строка-образец задается строковым литералом, константной времени компиляции. + * Места вставки обозначаются либо как `{}`, либо как `{номер}`. + * В случае без указания номера, параметры подставляются в переданном в функцию порядке. В случае указания номера, параметры подставляются в соответствии + * с указанным порядковым номером. Нумерация параметров начинается с 1. Смешивать параметры без номера и с номером нельзя - используется только + * один из вариантов для всех подстановок. В случае указания номеров - один параметр может участвовать в нескольких подстановках. + * Все переданные параметры должны участвовать в подстановках. Для вставки самих фигурных скобок они должны удваиваться - `{{`, `}}`. + * Строка-образец обрабатывается во время компиляции, и для неё сразу создаётся массив с информацией о вставках - какие части строки копировать + * в результат, в какие места вставлять переданные значения. Из-за невозможности вывести компилятором тип для объекта с этой информацией напрямую из + * строкового литерала, его приходится указывать дважды - первый раз как параметр для вывода типа, второй - как параметр для конструктора + * типа, выведенного из первого параметра. Для упрощения задания образца используется макрос `S_FRM("образец{}")`, который просто повторяет параметр + * два раза. + * Функция не является заменой `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. + * @tparam T - type of the pattern string, inferred from the argument. + * @tparam Args... - types of arguments passed. + * @param str_pattern - The pattern string, a string literal, is used to infer the character type of an expression and obtain the length of the pattern at compile time. + * @param pattern - information parsed during compilation in the template string, containing the lengths of text chunks and places to insert parameters. + * @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. Because the compiler cannot deduced the type for an object with this information directly from + * a string literal, it has to be specified twice - the first time as a parameter for type deducing, the second time as a parameter for the constructor + * the type deduced from the first parameter. To simplify specifying a sample, use the macro `S_FRM("sample{}")`, which simply repeats the parameter twice. + * 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(S_FRM(u"Test {} from {}, {}."), from, total, success ? u"success"_ss : u"fail"_ss); + * ``` + */ +template requires (sizeof...(Args) > 0) +constexpr auto e_subst(T&& str_pattern, const details::subst_params::symb_type, std::type_identity_t, std::type_identity_t...>& pattern, Args&&...args) { + return expr_subst::symb_type, T, Args...>{pattern, std::forward(args)...}; +} + +#define S_FRM(s) s, s + /*! * @ru @brief Небольшое пространство для методов работы со стандартными строками. * @en @brief Small namespace for standard string methods. @@ -5464,27 +5945,26 @@ struct replace_grow_helper { reserve_for_copy = dst_str->data(); } } - K* dst_start = reserve_for_copy; 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::move(dst_start + pos + all_delta, src_start + pos, lenOfPiece); + ch_traits::move(reserve_for_copy + pos + all_delta, src_start + pos, lenOfPiece); if constexpr (std::is_same_v) { - ch_traits::copy(dst_start + pos + all_delta - replLen, repl, replLen); + ch_traits::copy(reserve_for_copy + pos + all_delta - replLen, repl, replLen); } else { if (!repl) { - repl = dst_start + pos + all_delta - replLen; + repl = reserve_for_copy + pos + all_delta - replLen; expr.place(repl); } else { - ch_traits::copy(dst_start + pos + all_delta - replLen, repl, replLen); + ch_traits::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::copy(dst_start, src_start, found[0]); + ch_traits::copy(reserve_for_copy, src_start, found[0]); str = std::move(*dst); } } diff --git a/readme.md b/readme.md index 619798b..c349f4d 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ [![CMake on multiple platforms](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml) -Version 1.5.0. +Version 1.6.0. On Russian | По-русски @@ -302,8 +302,8 @@ function(add_simstr) simstr GIT_REPOSITORY https://github.com/orefkov/simstr.git GIT_SHALLOW TRUE - GIT_TAG tags/rel1.5.0 # Укажите нужный релиз - FIND_PACKAGE_ARGS NAMES simstr 1.5.0 + GIT_TAG tags/rel1.6.0 # Укажите нужный релиз + FIND_PACKAGE_ARGS NAMES simstr 1.6.0 ) FetchContent_MakeAvailable(simstr) endfunction() diff --git a/readme_ru.md b/readme_ru.md index 51f19b6..6f50b63 100644 --- a/readme_ru.md +++ b/readme_ru.md @@ -3,7 +3,7 @@ [![CMake on multiple platforms](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml) -Версия 1.5.0. +Версия 1.6.0. On English | По-английски @@ -303,8 +303,8 @@ function(add_simstr) simstr GIT_REPOSITORY https://github.com/orefkov/simstr.git GIT_SHALLOW TRUE - GIT_TAG tags/rel1.5.0 # Укажите нужный релиз - FIND_PACKAGE_ARGS NAMES simstr 1.5.0 + GIT_TAG tags/rel1.6.0 # Укажите нужный релиз + FIND_PACKAGE_ARGS NAMES simstr 1.6.0 ) FetchContent_MakeAvailable(simstr) endfunction() diff --git a/src/sstring.cpp b/src/sstring.cpp index a8a644c..dc9b69b 100644 --- a/src/sstring.cpp +++ b/src/sstring.cpp @@ -1,5 +1,5 @@ /* - * ver. 1.5.0 + * ver. 1.6.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com * Реализация строковых функций * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab70cae..9f769ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ # add_executable(test_str test_str.cpp) -add_executable(test_expr_only test_expr_only.cpp) +add_executable(test_expr_only test_expr_only.cpp test_tostrexpr.cpp) target_link_libraries(test_str simstr::simstr GTest::gtest_main) target_link_libraries(test_expr_only GTest::gtest_main) diff --git a/tests/test_expr_only.cpp b/tests/test_expr_only.cpp index 4051770..824f6d9 100644 --- a/tests/test_expr_only.cpp +++ b/tests/test_expr_only.cpp @@ -1,5 +1,5 @@ /* - * ver. 1.5.0 + * ver. 1.6.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com * Тесты simstr * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com @@ -62,6 +62,15 @@ TEST(StrExpr, Spaces) { EXPECT_EQ(testu, u"abc__________cde"); } +TEST(StrExpr, PlusString) { + std::string t = "aa"_ss + "bb"s; + EXPECT_EQ(t, "aabb"); + const std::string add = "bb"; + std::string r = add + "aa"_ss; + EXPECT_EQ(r, "bbaa"); + +} + TEST(StrExpr, Repeat) { std::string testa = "abc"; testa = e_repeat(+testa + " " + 10 + "s.", 3); @@ -142,7 +151,7 @@ TEST(StrExpr, Join) { TEST(StrExpr, Replace) { std::string testa = e_repl("test"_ss, "t"sv, "-|-"s) + 10; EXPECT_EQ(testa, "-|-es-|-10"); - testa = e_repl("aaaaaaaaaaaaaaaa"_ss, "a", "bb"); + testa = e_repl("aaaaaaaaaaaaaaaa", "a", "bb"); EXPECT_EQ(testa, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); testa = e_repl("aaaaaaaaaaaaaaaa"_ss, "a", "") + "-"; @@ -302,4 +311,27 @@ TEST(StrExpr, StrReplace) { } } +TEST(StrExpr, Concat) { + std::string tt; + std::string c = e_concat(eea + "," + " ", u8"bb", tt, 1, e_if(true, "--"), e_hex(16), 1.2); + EXPECT_EQ(c, "bb, , 1, --, 0x10, 1.2"); + + std::string_view text = "testes"; + int count = 10; + std::string t = e_concat("", text, " = ", count, " times."); + EXPECT_EQ(t, "testes = 10 times."); +} + +TEST(StrExpr, Subst) { + const auto ttt = "test"_ss; + int ii = 3; + std::string t = e_subst(S_FRM("Test {{--}} {}=, {}"), ttt, ii); + EXPECT_EQ(t, "Test {--} test=, 3"); + t = e_subst(S_FRM("Test {2}={1}, {2}, {1}"), "test", 2); + EXPECT_EQ(t, "Test 2=test, 2, test"); + + std::u16string u16t = e_subst(S_FRM(u"Test {}={}, {}"), u"test", 2, u"test"sv); + EXPECT_EQ(u16t, u"Test test=2, test"); +} + } // namespace simstr::tests diff --git a/tests/test_str.cpp b/tests/test_str.cpp index ba87b8e..506a00b 100644 --- a/tests/test_str.cpp +++ b/tests/test_str.cpp @@ -1,5 +1,5 @@ /* - * ver. 1.5.0 + * ver. 1.6.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com * Тесты simstr * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com @@ -1797,17 +1797,6 @@ TEST(SimStr, ExprRepeat) { EXPECT_EQ(std::string{e_repeat("aa"_ss + t + "_", 3)}, "aa1_aa1_aa1_"); } -TEST(SimStr, Constexpr) { - constexpr ssa tt = " asd "_ss.trimmed(); - static_assert(tt == "asd"); - constexpr stringa aa{"asd"}; - static_assert(aa == "asd"); - static_assert(aa.length() == 3); - constexpr stringa bb = ""; - constexpr int k = "123"_ss.to_int().value; - static_assert(k == 123); -} - TEST(SimStr, StrExpToStdString) { std::basic_string, std::pmr::polymorphic_allocator> test = "count = "_ss + 10 + " times"; EXPECT_EQ(test, "count = 10 times"); @@ -2003,6 +1992,13 @@ TEST(SimStr, EFill) { EXPECT_EQ(test, "t=10______>"); } +TEST(SimStr, Subst) { + int from = 1, total = 100; + bool success = true; + lstringu<100> u16t = e_subst(S_FRM(u"Test {} from {}, {}."), from, total, e_choice(success, u"success", u"fail")); + EXPECT_EQ(u16t, u"Test 1 from 100, success."); +} + } // namespace simstr::tests TEST(SimStr, StrNoNamespace) { diff --git a/tests/test_tostrexpr.cpp b/tests/test_tostrexpr.cpp new file mode 100644 index 0000000..85d57ea --- /dev/null +++ b/tests/test_tostrexpr.cpp @@ -0,0 +1,307 @@ +/* + * ver. 1.6.0 + * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com + * Тесты simstr + * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com + * Test of simstr + */ + +#include "../include/simstr/strexpr.h" +#include +#include +#include + +using namespace std::literals; + +namespace simstr::tests { +/*! + * @ru + * @defgroup ConvertToStrExpr Конвертация типов в в строковые выражения + * Различные способы конвертации типов в строковые выражения. + * Если вы реализуете один (и только один) из способов преобразования вашего типа в строковое выражение, + * то сможете использовать ваш тип напрямую в операциях конкатенации со строковыми выражениями и как + * аргументы в функциях `e_concat` и `e_subst`. + * @en + * @defgroup ConvertToStrExpr Converting types to string expressions + * Various ways to convert types to string expressions. + * If you implement one (and only one) of the ways to convert your type to a string expression, + * then you can use your type directly in concatenation operations with string expressions and how + * arguments in the `e_concat` and `e_subst` functions. + */ + +/*! + * @ingroup ConvertToStrExpr + * @ru @page method1 Способ 1 + * Просто реализуйте в своём типе требования, чтобы он являлся строковым выражением. + * @en @page method1 Method 1 + * Just implement the requirement in your type that it be a string expression. + * @ru @par Пример: + * @en @par Example: + * @~ + * @snippet test_tostrexpr.cpp Method1 + */ + +//! [Method1] +struct add_exclamation { + ssa text_; + unsigned count_; + + // symb_type + using symb_type = char; + // length + size_t length() const noexcept { + return text_.length() + count_; + } + // place + char* place(char* ptr) const noexcept{ + ptr = text_.place(ptr); + std::char_traits::assign(ptr, count_, '!'); + return ptr + count_; + } +}; + +TEST(ToStrExpr, CheckExclamation) { + std::string test = "Msg is <" + add_exclamation{"Happy Birthday", 5} + ">"; + EXPECT_EQ(test, "Msg is "); + + add_exclamation msg{"Happy Birthday", 3}; + test = e_concat("", "Msg is <", msg, ">"); + EXPECT_EQ(test, "Msg is "); + + const add_exclamation cmsg{"Happy Birthday", 0}; + test = e_subst(S_FRM("Msg is <{}>"), cmsg); + EXPECT_EQ(test, "Msg is "); +} +//! [Method1] + +/*! + * @ingroup ConvertToStrExpr + * @ru @page method2 Способ 2 + * Создайте тип-обёртку, который является строковым выражением и может инициализироваться + * вашим типом. После задайте их соответствие с помощью специализации шаблона `convert_to_strexpr`. + * @en @page method2 Method 2 + * Create a wrapper type that is a string expression and can be initialized + * your type. Then set their correspondence using the `convert_to_strexpr` template specialization. + * @ru @par Пример: + * @en @par Example: + * @~ + * @snippet test_tostrexpr.cpp Method2 + */ + +//! [Method2] + +// Тип / Type +struct car_info { + std::string model; + int year; +}; + +// Обёртка для превращения его в строковое выражение +// Wrapper to turn it into a string expression +struct car_info_expr { + const car_info& car_; + expr_num year; + + car_info_expr(const car_info& car) : car_(car), year(car.year){} + inline static constexpr ssa ModelTag = "Model: "; + inline static constexpr ssa YearTag = ", Year: "; + + using symb_type = char; + + size_t length() const { + return ModelTag.length() + car_.model.length() + YearTag.length() + year.length(); + } + + char* place(char* ptr) const { + ptr = ModelTag.place(ptr); + std::char_traits::copy(ptr, car_.model.data(), car_.model.length()); + ptr += car_.model.length(); + ptr = YearTag.place(ptr); + return year.place(ptr); + } +}; + +} // namespace simstr::tests + + +namespace simstr { + +// Специализируем шаблон, задавая соответствие типа и его обёртки +// Specialize the template by specifying a match between the type and its wrapper +template<> +struct convert_to_strexpr { + // Указываем тип, который будет "обёрткой" для нашего типа + // Specify the type that will be a "wrapper" for our type + using type = tests::car_info_expr; +}; + +} // namespace simstr + +namespace simstr::tests { + +TEST(ToStrExpr, CheckCarInfo) { + car_info ci{"Ford", 2020}; + std::string test = "Car is <"_ss + ci + ">"; + EXPECT_EQ(test, "Car is "); + + ci.year++; + test = e_concat("", "Car is <", ci, ">"); + EXPECT_EQ(test, "Car is "); + + ci.year++; + test = e_subst(S_FRM("Car is <{}>"), ci); + EXPECT_EQ(test, "Car is "); +} +//! [Method2] + +/*! + * @ingroup ConvertToStrExpr + * @ru @page method3 Способ 3 + * Специализируйте шаблон `convert_to_strexpr` для вашего типа и создайте в нём статическую + * функцию `convert`, принимающую ваш объект и возвращающую строковое выражение, строковый объект + * simstr или std::basic_string. + * @en @page method3 Method 3 + * Specialize the `convert_to_strexpr` template for your type and create a static + * `convert` function in it that takes your object and returns a string expression, a simstr string object, + * or std::basic_string. + * @ru @par Пример: + * @en @par Example: + * @~ + * @snippet test_tostrexpr.cpp Method3 + */ + +//! [Method3] + +struct animal { + std::string name_; + std::string sound_; +}; + +} //namespace simstr::tests + +namespace simstr { + +// Специализируем шаблон, задавая соответствие типа и его обёртки +// Specialize the template by specifying a match between the type and its wrapper +template<> +struct convert_to_strexpr { + // Создаём функцию `convert`, которая вернёт строковое представление объекта + // Create a function `convert`, that will return a string representation of the object + static auto convert(const tests::animal& a) { + // Так делать нельзя - операция + складывает в выражение ссылки на временные объекты, + // которые разрушаются после ";", и возвращать такой объект нельзя. + // This can't be done - the operator+ adds references to temporary objects to the expression, + // which are destroyed after ";", and such an object cannot be returned. + //return "Animal: " + a.name_ + ", Sound: " + a.sound_; + + // А вот такой можно - он не хранит ссылки на временные объекты + // But this one is possible - it doesn't store references to temporary objects + return e_concat("", "Animal: ", a.name_, ", Sound: ", a.sound_); + // Также можно возвращать просто std::string, stringa, lstringa + // You can also return just std::string, stringa, lstringa + } +}; + +} //namespace simstr + +namespace simstr::tests { + +TEST(ToStrExpr, CheckAnimal) { + animal cat{"Cat", "Meow"}; + + std::string test = "<"_ss + cat + ">"; + EXPECT_EQ(test, ""); + + const animal dog{"Dog", "Woof"}; + test = e_concat("", "<", dog, ">"); + EXPECT_EQ(test, ""); + + test = e_subst(S_FRM("\\_{}_/"), animal{"Snake", "Pssstt"}); + EXPECT_EQ(test, "\\_Animal: Snake, Sound: Pssstt_/"); +} +//! [Method3] + +/*! + * @ingroup ConvertToStrExpr + * @ru @page method4 Способ 4 + * Просто в своём типе сделайте функцию `template auto to_strexpr()const`, которая возвращает + * строковое выражение или строковый объект. + * @en @page method4 Method 4 + * Simply create a `template auto to_strexpr()const` function in your type that returns a + * string expression or string object. + * @ru @par Пример: + * @en @par Example: + * @~ + * @snippet test_tostrexpr.cpp Method4 + */ + +//! [Method4] + +struct test { + int number; + int from; + // Сделаем две отдельные реализации: для char (попроще) и для остальных типов (там придётся возвращать не литералы, а simple_str, и требуется копия) + // Let's make two separate implementations: for char (simpler) and for other types (there we'll have to return not literals, but simple_str, and a copy is required) + template requires (std::is_same_v) + auto to_strexpr() const { + return e_concat("", "test ", number, " from ", from); + } + + template requires (!std::is_same_v) + auto to_strexpr() const { + // uni_string возвращает локальный объект, а ссылку на них нельзя возвращать из функции, + // поэтому в e_concat надо форсировать сохранение по копии, а не по ссылке. + // uni_string returns a local object, and references to them cannot be returned from a function, + // so in e_concat we need to force saving by copy, not by reference. + return e_concat(force_copy{empty_expr{}}, force_copy{uni_string(K, "test ")}, number, force_copy{uni_string(K, " from ")}, from); + } +}; + +TEST(ToStrExpr, CheckTest) { + test t1{1, 10}; + + std::string t = "Begin <"_ss + t1 + ">"; + EXPECT_EQ(t, "Begin "); + + const test t2{8, 12}; + + t = e_concat("", "<", t2, ">"); + EXPECT_EQ(t, ""); + + t = e_subst(S_FRM("<{}>"), test{99, 100}); + EXPECT_EQ(t, ""); + + // Проверим работу для не char + // Let's check the work for non-char + std::wstring wt = L"aaa "_ss + test{99, 100}; + EXPECT_EQ(wt, L"aaa test 99 from 100"); + + std::u16string ut = u"aaa "_ss + test{99, 100}; + EXPECT_EQ(ut, u"aaa test 99 from 100"); +} +//! [Method4] + +TEST(ToStrExpr, ConcatCustom) { + std::string tt; + std::string c = e_concat(eea + "," + " ", u8"bb", tt, 1, e_if(true, "--"), e_hex(16), 1.2, test{10, 30}); + EXPECT_EQ(c, "bb, , 1, --, 0x10, 1.2, test 10 from 30"); + + std::string_view text = "testes"; + int count = 10; + std::string t = e_concat("", text, " = ", count, " times."); + EXPECT_EQ(t, "testes = 10 times."); +} + +TEST(ToStrExpr, SubstCustom) { + const auto ttt = "test"_ss; + int ii = 3; + const test tr{10, 10}; + std::string t = e_subst(S_FRM("Test {{--}} {}={}, {}"), ttt, tr, ii); + EXPECT_EQ(t, "Test {--} test=test 10 from 10, 3"); + t = e_subst(S_FRM("Test {2}={1}, {2}, {1}"), "test", 2); + EXPECT_EQ(t, "Test 2=test, 2, test"); + + std::u16string u16t = e_subst(S_FRM(u"Test {}={}, {}"), u"test", 2, u"test"sv); + EXPECT_EQ(u16t, u"Test test=2, test"); +} +} // namespace simstr::tests