From 35d2ea2ee85773dd6156d16e41a60363792e5f6c Mon Sep 17 00:00:00 2001 From: Aleksandr Orefkov Date: Sun, 25 Jan 2026 13:39:22 +0300 Subject: [PATCH] 1.5.0. --- CMakeLists.txt | 6 +- docs/Doxyfile | 2 +- docs/Doxyfile_ru | 2 +- include/simstr/simple_unicode.h | 2 +- include/simstr/sstring.h | 12 +- include/simstr/strexpr.h | 199 +++++++++++++++++++++++--------- readme.md | 17 ++- readme_ru.md | 17 ++- src/sstring.cpp | 5 +- tests/test_expr_only.cpp | 18 ++- tests/test_str.cpp | 31 ++++- 11 files changed, 232 insertions(+), 79 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af6639f..4d65535 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include(FetchContent) project( simstr - VERSION 1.4.0 + VERSION 1.5.0 DESCRIPTION "Yet another modern C++ string library" HOMEPAGE_URL "https://github.com/orefkov/simstr" LANGUAGES CXX @@ -116,7 +116,7 @@ if(SIMSTR_BUILD_TESTS) googletest # Specify the commit you depend on and update it regularly. URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip - FIND_PACKAGE_ARGS NAMES GTest 1.17.0 + FIND_PACKAGE_ARGS NAMES "GTest 1.17.0" ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt FALSE CACHE BOOL "" FORCE) @@ -134,7 +134,7 @@ function(GBencmark) googlebench # Specify the commit you depend on and update it regularly. URL https://github.com/google/benchmark/archive/refs/tags/v1.9.4.zip - FIND_PACKAGE_ARGS NAMES benchmark 1.9.4 + FIND_PACKAGE_ARGS NAMES "benchmark 1.9.4" ) set(BENCHMARK_ENABLE_TESTING OFF) set(BENCHMARK_ENABLE_LTO OFF) diff --git a/docs/Doxyfile b/docs/Doxyfile index b5923db..96c6834 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.4.0 +PROJECT_NUMBER = 1.5.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 diff --git a/docs/Doxyfile_ru b/docs/Doxyfile_ru index 82a05c3..e88e5db 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.4.0 +PROJECT_NUMBER = 1.5.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 diff --git a/include/simstr/simple_unicode.h b/include/simstr/simple_unicode.h index d047d08..d7328de 100644 --- a/include/simstr/simple_unicode.h +++ b/include/simstr/simple_unicode.h @@ -1,6 +1,6 @@ /* * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com - * ver. 1.0 + * ver. 1.5.0 */ #pragma once diff --git a/include/simstr/sstring.h b/include/simstr/sstring.h index 511b68f..a06e3b9 100644 --- a/include/simstr/sstring.h +++ b/include/simstr/sstring.h @@ -1,9 +1,9 @@ /* * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com - * ver. 1.4.0 + * ver. 1.5.0 * Классы для работы со строками * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com -* ver. 1.4.0 +* ver. 1.5.0 * Classes for working with strings */ @@ -314,7 +314,13 @@ public: return {}; } #endif - return impl_to_double(ptr, ptr + len); + if constexpr (sizeof(K) == 1) { + return impl_to_double((const char*)ptr, (const char*)ptr + len); + } else if constexpr (sizeof(K) == 2) { + return impl_to_double((const char16_t*)ptr, (const char16_t*)ptr + len); + } else { + return impl_to_double((const char32_t*)ptr, (const char32_t*)ptr + len); + } } /*! * @ru @brief Преобразовать строку в double. diff --git a/include/simstr/strexpr.h b/include/simstr/strexpr.h index 4fa4991..aa91382 100644 --- a/include/simstr/strexpr.h +++ b/include/simstr/strexpr.h @@ -1,5 +1,5 @@ /* - * ver. 1.4.0 + * ver. 1.5.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com * База для строковых конкатенаций через выражения времени компиляции * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com @@ -3282,7 +3282,7 @@ public: return {}; } double d{}; - if (std::from_chars(ptr, ptr + len, d).ec == std::errc{}) { + if (std::from_chars((const u8s*)ptr, (const u8s*)ptr + len, d).ec == std::errc{}) { return d; } return {}; @@ -4477,32 +4477,20 @@ constexpr auto e_repl(A&& w, T&& p, X&& r) { /*! * @ingroup StrExprs - * @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки. + * @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки на другую строку. * @tparam K - тип строки. - * @details `e_repl` позволяет заменять только с использование строковых литералов. - * В случае, когда искомая подстрока или строка замены не известны при компиляции, и задаются в runtime, - * следует использовать этот тип, например: - * @en @brief A string expression that generates a string replacing all occurrences of the given substring. + * @en @brief A string expression that generates a string replacing all occurrences of the given substring to another string. * @tparam K - string type. - * @details `e_repl` only allows replacement using string literals. - * In the case when the required substring or replacement string is not known at compilation, and is set at runtime, - * this type should be used, for example: - * @~ - * ```cpp - * stringa result = "
" + expr_replaced{source, pattern, repl} + "
"; - * ``` */ -template +template struct expr_replaced : expr_to_std_string> { using symb_type = K; using my_type = expr_replaced; str_src what; const str_src pattern; - mutable K* replStart; - mutable size_t replLen; + const str_src repl; mutable find_all_container matches_; mutable size_t last_; - const E& expr; /*! * @ru @brief Конструктор. * @param w - исходная строка. @@ -4513,20 +4501,17 @@ struct expr_replaced : expr_to_std_string> { * @param p - the searched substring. * @param r - replacement string. */ - constexpr expr_replaced(str_src w, str_src p, const K* r, size_t rl, const E& e) : what(w), pattern(p), replStart(const_cast(r)), replLen(rl), expr(e) {} + constexpr expr_replaced(str_src w, str_src p, str_src r) : what(w), pattern(p), repl(r) {} constexpr size_t length() const { - size_t l = what.length(), plen = pattern.length(); - if constexpr (!std::is_same_v) { - replLen = expr.length(); - } - if (!plen || plen == replLen) { + 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(replLen - plen) * matches_.added_; + l += int(rlen - plen) * matches_.added_; if (matches_.added_ == FIND_CACHE_SIZE) { for (;;) { @@ -4535,7 +4520,7 @@ struct expr_replaced : expr_to_std_string> { break; } last_ = next + plen; - l += replLen - plen; + l += rlen - plen; } } } @@ -4545,8 +4530,8 @@ struct expr_replaced : expr_to_std_string> { return l; } constexpr K* place(K* ptr) const noexcept { - size_t plen = pattern.length(); - if (plen == replLen) { + 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); @@ -4556,17 +4541,8 @@ struct expr_replaced : expr_to_std_string> { size_t delta = next - start; ch_traits::copy(ptr, from + start, delta); ptr += delta; - if constexpr (std::is_same_v) { - ch_traits::copy(ptr, replStart, replLen); - } else { - if (!replStart) { - replStart = ptr; - expr.place(replStart); - } else { - ch_traits::copy(ptr, replStart, replLen); - } - } - ptr += replLen; + ch_traits::copy(ptr, repl.symbols(), rlen); + ptr += rlen; start = next + plen; } return ptr; @@ -4581,21 +4557,12 @@ struct expr_replaced : expr_to_std_string> { for (size_t start = 0, offset = matches_.positions_[0], idx = 1; ;) { ch_traits::copy(ptr, from + start, offset - start); ptr += offset - start; - if constexpr (std::is_same_v) { - ch_traits::copy(ptr, replStart, replLen); - } else { - if (!replStart) { - replStart = ptr; - expr.place(replStart); - } else { - ch_traits::copy(ptr, replStart, replLen); - } - } - ptr += replLen; + ch_traits::copy(ptr, repl.symbols(), rlen); + ptr += rlen; start = offset + plen; if (start >= last_) { - size_t tail = what.length() - last_; - ch_traits::copy(ptr, from + last_, tail); + size_t tail = what.length() - start; + ch_traits::copy(ptr, from + start, tail); ptr += tail; break; } else { @@ -4606,6 +4573,130 @@ struct expr_replaced : expr_to_std_string> { } }; +/*! + * @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 E> +struct expr_replaced_e : expr_to_std_string> { + using symb_type = K; + using my_type = expr_replaced; + str_src what; + const str_src pattern; + mutable size_t replLen; + mutable find_all_container 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 w, str_src 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::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::copy(ptr, from + start, delta); + ptr += delta; + ch_traits::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::copy(ptr, from + start, delta); + ptr += delta; + ch_traits::copy(ptr, repl, replLen); + ptr += replLen; + start = next + plen; + } + } + size_t tail = what.length() - start; + ch_traits::copy(ptr, from + start, tail); + return ptr + tail; + } +}; + /*! * @ingroup StrExprs * @ru @brief Получить строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки. @@ -4624,7 +4715,7 @@ template, typename T, typename X> 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.str, repl.len, 0}; + return expr_replaced{get_str_src_from(std::forward(w)), pattern, repl}; } /*! @@ -4644,7 +4735,7 @@ 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{get_str_src_from(std::forward(w)), pattern, nullptr, 0, expr}; + return expr_replaced_e{get_str_src_from(std::forward(w)), pattern, expr}; } template diff --git a/readme.md b/readme.md index 9ec19ed..619798b 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.4.0. +Version 1.5.0. On Russian | По-русски @@ -47,7 +47,7 @@ If you actively used `std::string_view` and understood its advantages and disadv then the `simstr` approach will also be clear to you. ## Main features of the library -When using only `#include "simstr\strexpr.h"`: +When using only `#include "simstr/strexpr.h"`: - Support for working with strings `char`, `char8_t`, `char16_t`, `char32_t`, `wchar_t`. - Powerful and extensible *"String Expressions"* system. Allows you to efficiently implement the conversion and addition (concatenation) of strings, string literals, numbers (and possibly other objects), @@ -65,6 +65,11 @@ When using only `#include "simstr\strexpr.h"`: - Merging (join) containers of strings into a single string, with specifying delimiters and options - "skip empty", "delimiter after last". - Splitting strings into parts by a specified delimiter. Splitting is possible immediately into a container with strings, or by calling a functor for each substring, or by iterating using the `Splitter` iterator. +- Functions for modifying standard strings with string expressions: + - str::append, str::prepend, str::insert, str::change - modify str::string with string expressions, + for example, `str::append(text, "count = "_ss + count + " times")`. + - str::replace - replaces occurrences of the searched substring with the replacement string or string expression. + If the substring is not found, the string expression is not even evaluated. - Parsing integers with the possibility of "fine" tuning at compile time - you can set options for checking overflow, skipping whitespace characters, a specific base or auto-selection by prefixes `0x`, `0`, `0b`, `0o`, admissibility of the `+` sign. Parsing is implemented for all types of strings and characters. @@ -277,9 +282,9 @@ When connecting only `strexpr.h` - the types `simple_str` and `simple_str_nt< - [Description of the "Expression Templates" technique used](https://habr.com/ru/articles/936468/) (On Russian) ## Usage -The library can be used partially, just by taking the file `"include\simstr\strexpr.h"` and including it in your sources +The library can be used partially, just by taking the file `"include/simstr/strexpr.h"` and including it in your sources ```cpp -#include "include\simstr\strexpr.h" +#include "include/simstr/strexpr.h" ``` This will only connect string expressions and simplified implementations of `simple_str` and `simple_str_nt`, without UTF and Unicode functions. @@ -297,8 +302,8 @@ function(add_simstr) simstr GIT_REPOSITORY https://github.com/orefkov/simstr.git GIT_SHALLOW TRUE - GIT_TAG tags/rel1.4.0 # Укажите нужный релиз - FIND_PACKAGE_ARGS NAMES simstr 1.4.0 + GIT_TAG tags/rel1.5.0 # Укажите нужный релиз + FIND_PACKAGE_ARGS NAMES simstr 1.5.0 ) FetchContent_MakeAvailable(simstr) endfunction() diff --git a/readme_ru.md b/readme_ru.md index 1acf215..51f19b6 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.4.0. +Версия 1.5.0. On English | По-английски @@ -48,7 +48,7 @@ то подход `simstr` вам также будет понятен. ## Основные возможности библиотеки -При использовании только `#include "simstr\strexpr.h"`: +При использовании только `#include "simstr/strexpr.h"`: - Поддержка работы со строками `char`, `char8_t`, `char16_t`, `char32_t`, `wchar_t`. - Мощная и расширяемая система *"Строковых выражений"*. Позволяет эффективно реализовать преобразование и сложение (конкатенацию) строк, строковых литералов, чисел (и возможно других объектов), @@ -66,6 +66,11 @@ - Слияние (join) контейнеров строк в единую строку, с заданием разделителей и опций - "пропускать пустые", "разделитель после последней". - Разбиение (split) строк на части по заданному разделителю. Разбиение возможно сразу в контейнер со строками, либо вызовом функтора для каждой подстроки, либо путем итерации с помощью итератора `Splitter`. +- Функции модификации стандартных строк строковыми выражениями: + - str::append, str::prepend, str::insert, str::change - меняют str::string строковыми выражениями, + например `str::append(text, "count = "_ss + count)`. + - str::replace - заменяет вхождения искомой подстроки на строку замены или строковое выражение. + Если подстрока не найдена, строковое выражение даже не вычисляется. - Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения, пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам `0x`, `0`, `0b`, `0o`, допустимость знака `+`. Парсинг реализован для всех видов строк и символов. @@ -278,9 +283,9 @@ int split_and_calc_total_sim(ssa numbers, ssa delimiter) { - [Описание применяемой техники "Expression Templates"](https://habr.com/ru/articles/936468/) ## Использование -Библиотеку можно использовать частично, просто взяв файл `"include\simstr\strexpr.h"` и включив в свои исходники +Библиотеку можно использовать частично, просто взяв файл `"include/simstr/strexpr.h"` и включив в свои исходники ```cpp -#include "include\simstr\strexpr.h" +#include "include/simstr/strexpr.h" ``` Это подключит только строковые выражения и упрощённые реализации `simple_str` и `simple_str_nt`, без функций работы с UTF и Unicode. @@ -298,8 +303,8 @@ function(add_simstr) simstr GIT_REPOSITORY https://github.com/orefkov/simstr.git GIT_SHALLOW TRUE - GIT_TAG tags/rel1.4.0 # Укажите нужный релиз - FIND_PACKAGE_ARGS NAMES simstr 1.4.0 + GIT_TAG tags/rel1.5.0 # Укажите нужный релиз + FIND_PACKAGE_ARGS NAMES simstr 1.5.0 ) FetchContent_MakeAvailable(simstr) endfunction() diff --git a/src/sstring.cpp b/src/sstring.cpp index 52d4b7c..a8a644c 100644 --- a/src/sstring.cpp +++ b/src/sstring.cpp @@ -1,7 +1,9 @@ /* + * ver. 1.5.0 * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com - * ver. 1.0 * Реализация строковых функций + * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com + * Implementation of string functions */ #include "simstr/simple_unicode.h" #include "simstr/sstring.h" @@ -1466,6 +1468,5 @@ SIMSTR_API std::optional impl_to_double(const K* start, const K* end) { template SIMSTR_API std::optional impl_to_double(const u8s* start, const u8s* end); template SIMSTR_API std::optional impl_to_double(const u16s* start, const u16s* end); template SIMSTR_API std::optional impl_to_double(const u32s* start, const u32s* end); -template SIMSTR_API std::optional impl_to_double(const uws* start, const uws* end); } // namespace simstr diff --git a/tests/test_expr_only.cpp b/tests/test_expr_only.cpp index c0b3369..4051770 100644 --- a/tests/test_expr_only.cpp +++ b/tests/test_expr_only.cpp @@ -1,4 +1,12 @@ -#include "../include/simstr/strexpr.h" +/* + * ver. 1.5.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 @@ -284,6 +292,14 @@ TEST(StrExpr, StrReplace) { std::u16string src = u"-aaaaaaaaaaaaaaaa--"; EXPECT_EQ(str::replace(src, u"a", u"vv"_ss + 10, 5, 3), u"-aaaavv10vv10vv10aaaaaaaaa--"); } + { + std::u32string a = U"<" + e_repl(U"test"sv, U"t", U"a" + e_repl(U"test"s, U"es", U"se")) + U">"; + EXPECT_EQ(a, U""); + } + { + std::string a = u8"<" + e_repl("test"sv, "t", u8"a" + e_repl("test"s, "es", "se")) + ">"; + EXPECT_EQ(a, ""); + } } } // namespace simstr::tests diff --git a/tests/test_str.cpp b/tests/test_str.cpp index 8133f5b..ba87b8e 100644 --- a/tests/test_str.cpp +++ b/tests/test_str.cpp @@ -1,4 +1,12 @@ -#include +/* + * ver. 1.5.0 + * (c) Проект "SimStr", Александр Орефков orefkov@gmail.com + * Тесты simstr + * (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com + * Test of simstr + */ + +#include #include using namespace std::literals; @@ -1918,6 +1926,27 @@ TEST(SimStr, StrRepl) { a = e_repl("test"_ss, "t", "a"_ss + "1" + 10); EXPECT_EQ(a, "a110esa110"); + + a = e_repl("test"_ss, "x", "a"_ss + "1" + 10); + EXPECT_EQ(a, "test"); + + a = e_repl("test"_ss, "t", eea + "a"); + EXPECT_EQ(a, "aesa"); + + a = e_repl("tesd"_ss, "t", eea + "a"); + EXPECT_EQ(a, "aesd"); + + a = e_repl("tttt"_ss, "t", eea); + EXPECT_EQ(a, ""); + + a = e_repl("-tttttttttttttttttt-"_ss, "t", eea + "a"); + EXPECT_EQ(a, "-aaaaaaaaaaaaaaaaaa-"); + + a = e_repl("-tttttttttttttttttt-"_ss, "t", eea + "aa"); + EXPECT_EQ(a, "-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-"); + + a = "<" + e_repl("test"sv, "t", "a" + e_repl("test"s, "es", "se")) + ">"; + EXPECT_EQ(a, ""); } TEST(SimStr, HexEpr) {