This commit is contained in:
Aleksandr Orefkov 2026-01-25 13:39:22 +03:00
parent 620383a5a4
commit 35d2ea2ee8
11 changed files with 232 additions and 79 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,6 @@
/*
* (c) Проект "SimStr", Александр Орефков orefkov@gmail.com
* ver. 1.0
* ver. 1.5.0
*/
#pragma once

View File

@ -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.

View File

@ -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 = "<header>" + expr_replaced<u8s>{source, pattern, repl} + "</header>";
* ```
*/
template<typename K, typename E = int>
template<typename K>
struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
using symb_type = K;
using my_type = expr_replaced<K>;
str_src<K> what;
const str_src<K> pattern;
mutable K* replStart;
mutable size_t replLen;
const str_src<K> repl;
mutable find_all_container<FIND_CACHE_SIZE> matches_;
mutable size_t last_;
const E& expr;
/*!
* @ru @brief Конструктор.
* @param w - исходная строка.
@ -4513,20 +4501,17 @@ struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
* @param p - the searched substring.
* @param r - replacement string.
*/
constexpr expr_replaced(str_src<K> w, str_src<K> p, const K* r, size_t rl, const E& e) : what(w), pattern(p), replStart(const_cast<K*>(r)), replLen(rl), expr(e) {}
constexpr expr_replaced(str_src<K> w, str_src<K> p, str_src<K> r) : what(w), pattern(p), repl(r) {}
constexpr size_t length() const {
size_t l = what.length(), plen = pattern.length();
if constexpr (!std::is_same_v<E, int>) {
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<expr_replaced<K>> {
break;
}
last_ = next + plen;
l += replLen - plen;
l += rlen - plen;
}
}
}
@ -4545,8 +4530,8 @@ struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
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<expr_replaced<K>> {
size_t delta = next - start;
ch_traits<K>::copy(ptr, from + start, delta);
ptr += delta;
if constexpr (std::is_same_v<E, int>) {
ch_traits<K>::copy(ptr, replStart, replLen);
} else {
if (!replStart) {
replStart = ptr;
expr.place(replStart);
} else {
ch_traits<K>::copy(ptr, replStart, replLen);
}
}
ptr += replLen;
ch_traits<K>::copy(ptr, repl.symbols(), rlen);
ptr += rlen;
start = next + plen;
}
return ptr;
@ -4581,21 +4557,12 @@ struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
for (size_t start = 0, offset = matches_.positions_[0], idx = 1; ;) {
ch_traits<K>::copy(ptr, from + start, offset - start);
ptr += offset - start;
if constexpr (std::is_same_v<E, int>) {
ch_traits<K>::copy(ptr, replStart, replLen);
} else {
if (!replStart) {
replStart = ptr;
expr.place(replStart);
} else {
ch_traits<K>::copy(ptr, replStart, replLen);
}
}
ptr += replLen;
ch_traits<K>::copy(ptr, repl.symbols(), rlen);
ptr += rlen;
start = offset + plen;
if (start >= last_) {
size_t tail = what.length() - last_;
ch_traits<K>::copy(ptr, from + last_, tail);
size_t tail = what.length() - start;
ch_traits<K>::copy(ptr, from + start, tail);
ptr += tail;
break;
} else {
@ -4606,6 +4573,130 @@ struct expr_replaced : expr_to_std_string<expr_replaced<K>> {
}
};
/*!
* @ingroup StrExprs
* @ru @brief Строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки на строковое выражение.
* @tparam K - тип строки.
* @details Если искомая подстрока не найдена, то строковое выражение даже не вычисляется.
* Затем при осуществлении замены строковое выражение вычисляется только один раз в место первой замены,
* а в следующие места замен просто копируется символы из первого места. Это позволяет экономить память
* и время, если вам надо сделать замену на какую-либо "сборную" строку.
* @en @brief A string expression that generates a string replacing all occurrences of the given substring to string expression.
* @tparam K - string type.
* @details If the search substring is not found, the string expression is not even evaluated.
* Then, when performing a replacement, the string expression is evaluated only once at the first replacement location,
* and characters from the first location are simply copied to subsequent replacement locations. This saves memory
* and time if you need to replace with some kind of "composite" string.
*/
template<typename K, StrExprForType<K> E>
struct expr_replaced_e : expr_to_std_string<expr_replaced_e<K, E>> {
using symb_type = K;
using my_type = expr_replaced<K>;
str_src<K> what;
const str_src<K> pattern;
mutable size_t replLen;
mutable find_all_container<FIND_CACHE_SIZE> matches_;
mutable size_t last_;
const E& expr;
/*!
* @ru @brief Конструктор.
* @param w - исходная строка.
* @param p - искомая подстрока.
* @param e - строковое выражение для замены.
* @en @brief Constructor.
* @param w - source string.
* @param p - the searched substring.
* @param e - string expression to replace.
*/
constexpr expr_replaced_e(str_src<K> w, str_src<K> p, const E& e) : what(w), pattern(p), expr(e) {}
constexpr size_t length() const {
size_t l = what.length(), plen = pattern.length();
if (!plen) {
return l;
}
matches_.positions_[0] = what.find(pattern);
if (matches_.positions_[0] == -1) {
// Не нашли вхождений, нечего менять
return l;
}
matches_.added_ = 1;
// Вхождение есть, надо теперь получить длину замены
replLen = expr.length();
if (replLen == plen) {
// Замена той же длины, общая длина не изменится
return l;
}
what.find_all_to(matches_, pattern.symbols(), plen, matches_.positions_[0] + plen, FIND_CACHE_SIZE - 1);
last_ = matches_.positions_[matches_.added_ - 1] + plen;
l += int(replLen - plen) * matches_.added_;
if (matches_.added_ == FIND_CACHE_SIZE) {
for (;;) {
size_t next = what.find(pattern.symbols(), plen, last_);
if (next == str::npos) {
break;
}
last_ = next + plen;
l += replLen - plen;
}
}
if (!l) {
matches_.added_ = -1;
}
return l;
}
constexpr K* place(K* ptr) const noexcept {
if (matches_.added_ == 0) {
// не было найдено вхождений
return what.place(ptr);
} else if (matches_.added_ == -1) {
// Строка стала пустой
return ptr;
}
size_t plen = pattern.length();
const K* from = what.symbols();
ch_traits<K>::copy(ptr, from, matches_.positions_[0]);
ptr += matches_.positions_[0];
const K* repl = ptr;
expr.place((typename E::symb_type*)ptr);
ptr += replLen;
size_t start = matches_.positions_[0] + plen;
if (plen == replLen) {
for (;;) {
size_t next = what.find(pattern, start);
if (next == str::npos) {
break;
}
size_t delta = next - start;
ch_traits<K>::copy(ptr, from + start, delta);
ptr += delta;
ch_traits<K>::copy(ptr, repl, replLen);
ptr += replLen;
start = next + plen;
}
} else {
for (size_t idx = 1;;) {
if (start >= last_) {
break;
}
size_t next = idx < FIND_CACHE_SIZE ? matches_.positions_[idx++] : what.find(pattern, start);
size_t delta = next - start;
ch_traits<K>::copy(ptr, from + start, delta);
ptr += delta;
ch_traits<K>::copy(ptr, repl, replLen);
ptr += replLen;
start = next + plen;
}
}
size_t tail = what.length() - start;
ch_traits<K>::copy(ptr, from + start, tail);
return ptr + tail;
}
};
/*!
* @ingroup StrExprs
* @ru @brief Получить строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки.
@ -4624,7 +4715,7 @@ template<StrSource A, typename K = src_str_t<A>, typename T, typename X>
constexpr auto e_repl(A&& w, T&& p, X&& r) {
str_src<K> pattern{std::forward<T>(p)};
str_src<K> repl{std::forward<X>(r)};
return expr_replaced<K, int>{get_str_src_from(std::forward<A>(w)), pattern, repl.str, repl.len, 0};
return expr_replaced<K>{get_str_src_from(std::forward<A>(w)), pattern, repl};
}
/*!
@ -4644,7 +4735,7 @@ template<StrSource A, typename K = src_str_t<A>, typename T, StrExprForType<K> E
requires std::is_constructible_v<str_src<K>, T>
constexpr auto e_repl(A&& w, T&& p, const E& expr) {
str_src<K> pattern{std::forward<T>(p)};
return expr_replaced<K, E>{get_str_src_from(std::forward<A>(w)), pattern, nullptr, 0, expr};
return expr_replaced_e<K, E>{get_str_src_from(std::forward<A>(w)), pattern, expr};
}
template<bool UseVectorForReplace>

View File

@ -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.
<span class="obfuscator"><a href="readme_ru.md">On Russian | По-русски</a></span>
@ -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<K>` 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()

View File

@ -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.
<span class="obfuscator"><a href="readme.md">On English | По-английски</a></span>
@ -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()

View File

@ -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<double> impl_to_double(const K* start, const K* end) {
template SIMSTR_API std::optional<double> impl_to_double<u8s>(const u8s* start, const u8s* end);
template SIMSTR_API std::optional<double> impl_to_double<u16s>(const u16s* start, const u16s* end);
template SIMSTR_API std::optional<double> impl_to_double<u32s>(const u32s* start, const u32s* end);
template SIMSTR_API std::optional<double> impl_to_double<uws>(const uws* start, const uws* end);
} // namespace simstr

View File

@ -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 <gtest/gtest.h>
#include <list>
@ -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"<atsetesatset>");
}
{
std::string a = u8"<" + e_repl("test"sv, "t", u8"a" + e_repl("test"s, "es", "se")) + ">";
EXPECT_EQ(a, "<atsetesatset>");
}
}
} // namespace simstr::tests

View File

@ -1,4 +1,12 @@
#include <simstr/sstring.h>
/*
* ver. 1.5.0
* (c) Проект "SimStr", Александр Орефков orefkov@gmail.com
* Тесты simstr
* (c) Project "SimStr", Aleksandr Orefkov orefkov@gmail.com
* Test of simstr
*/
#include <simstr/sstring.h>
#include <gtest/gtest.h>
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, "<atsetesatset>");
}
TEST(SimStr, HexEpr) {