Fix find and rfind position handling in string and string_view (#1637)

* Add docs and tutorial for etl::intrusive_avl_tree (#1608)

* Add docs and tutorial for etl::intrusive_avl_tree

Documents the intrusive AVL tree added in PR #1425: a full API reference
page under Containers/Trees, and a tutorial demonstrating ordered access
to intrusively-linked objects with unpredictable lifetimes, including
move support and destructor-driven automatic removal from the tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add complexity shortcode and document its usage

Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling
for constant/logarithmic/linear/linearithmic/polynomial/exponential
classes) for rendering Big-O complexity inline, applies it throughout
the intrusive_avl_tree API reference in place of raw "O(...)" text, and
documents the Description/Complexity structure and supported classes in
DOCUMENTATION.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix issues found by @CodeRabbit

* Put complexity notes on their own lines in intrusive_avl_tree docs

Move the complexity shortcode from the inline "**Complexity:**" label
to its own line under a "**Complexity**" label. Notes that give several
alternatives (e.g. amortized vs. worst-case) now show each one on a
separate line. Update the guideline in DOCUMENTATION.md to match.

* Update complexity examples in DOCUMENTATION.md

Add the hard line break after the "**Complexity**" label in the
example, and rewrite the list of supported complexity classes in the
new two-line format, including an example with several alternatives.

* Add logarithmic complexity example to DOCUMENTATION.md

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>

* Updated sponsors pages

* Refactor the documentation css to group styles for light and dark themes

* Update Codacy badge in README.md

Updated Codacy badge link to reflect new project status.

* Fix Codacy badge display

* refresh codacy integration status

* Expose more Zephyr Kconfigs for error handling (#1622)

* zephyr: make `ETL_DEBUG` default to on if Zephyr assertions are enabled

this is the more appropriate default: ETL_DEBUG controls some container assertions, so it should follow the Zephyr assertions by default

* zephyr: expose new error-checking configs

* zephyr: add Kconfig choices for error handling

* Fix find and rfind position handling in string and string_view

* Default the rfind string_view position to npos

The string_view overload defaulted pos to 0, so rfind(view) only found
a match at the start of the string. The other rfind overloads and
std::basic_string use npos.

---------

Co-authored-by: Sergei <sergej.shirokov@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>
Co-authored-by: Eden Frosst <edenfrosst@gmail.com>
This commit is contained in:
Eminy 2026-10-29 14:44:10 +03:00 committed by GitHub
parent 60ba92928e
commit 55dcb84bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 212 additions and 18 deletions

View File

@ -707,6 +707,7 @@ size_t find(char c, size_t pos = 0) const;
```cpp
size_t rfind(const etl::istring& str, size_t pos = npos) const;
size_t rfind(const etl::string_view& view, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos, size_t n) const;
size_t rfind(char c, size_t pos = npos) const;

View File

@ -1476,6 +1476,11 @@ namespace etl
//*********************************************************************
size_type find(T c, size_type position = 0) const
{
if (position >= size())
{
return npos;
}
const_iterator i = etl::find(begin() + position, end(), c);
if (i != end())
@ -1504,7 +1509,7 @@ namespace etl
///\param pos The position to start searching from.
//*********************************************************************
template <typename TOtherTraits>
size_type rfind(const etl::basic_string_view<T, TOtherTraits>& view, size_type pos = 0) const
size_type rfind(const etl::basic_string_view<T, TOtherTraits>& view, size_type pos = npos) const
{
return rfind_impl(view.rbegin(), view.rend(), view.size(), pos);
}
@ -1544,12 +1549,8 @@ namespace etl
//*********************************************************************
size_type rfind(T c, size_type position = npos) const
{
if (position >= size())
{
position = size();
}
position = size() - position;
// Reverse offset of the first character to check, which is 'position' itself.
position = (position >= size()) ? 0 : (size() - position - 1);
const_reverse_iterator i = etl::find(rbegin() + static_cast<difference_type>(position), rend(), c);
@ -2851,11 +2852,16 @@ namespace etl
template <typename TIterator>
size_type find_impl(TIterator first, TIterator last, size_type sz, size_type pos = 0) const
{
if ((pos + sz) > size())
if ((pos > size()) || (sz > (size() - pos)))
{
return npos;
}
if (sz == 0)
{
return pos;
}
const_iterator iposition = etl::search(begin() + pos, end(), first, last);
if (iposition == end())
@ -2879,12 +2885,18 @@ namespace etl
return npos;
}
if (pos >= size())
if (pos > (size() - sz))
{
pos = size();
pos = size() - sz;
}
pos = size() - pos;
if (sz == 0)
{
return pos;
}
// Reverse offset of the last character of a match starting at 'pos'.
pos = size() - (pos + sz);
const_reverse_iterator iposition = etl::search(rbegin() + static_cast<difference_type>(pos), rend(), rfirst, rlast);

View File

@ -554,11 +554,16 @@ namespace etl
//*************************************************************************
ETL_CONSTEXPR14 size_type find(etl::basic_string_view<T, TTraits> view, size_type position = 0) const ETL_NOEXCEPT
{
if ((size() < view.size()))
if ((position > size()) || (view.size() > (size() - position)))
{
return npos;
}
if (view.empty())
{
return position;
}
const_iterator iposition = etl::search(begin() + position, end(), view.begin(), view.end());
if (iposition == end())
@ -598,9 +603,11 @@ namespace etl
position = etl::min(position, size() - view.size());
const_iterator iposition = etl::find_end(begin(), begin() + position + view.size(), view.begin(), view.end());
const_iterator search_end = begin() + position + view.size();
const_iterator iposition = etl::find_end(begin(), search_end, view.begin(), view.end());
if (iposition == end())
// find_end returns the end of the searched range when there is no match.
if ((iposition == search_end) && !view.empty())
{
return npos;
}

View File

@ -3815,6 +3815,33 @@ namespace
CHECK_EQUAL(TextL::npos, position2);
}
//*************************************************************************
TEST_FIXTURE(SetupFixture, test_find_rfind_all_positions)
{
const value_t the_haystack[] = STR("A haystack with a needle and another needle");
TextSTD compare_haystack(the_haystack);
TextL haystack(the_haystack);
const value_t* needles[] = {STR("needle"), STR("e"), STR("")};
// Every position up to past the end, where the search must not read outside the string.
for (size_t position = 0UL; position <= compare_haystack.size() + 2UL; ++position)
{
for (size_t i = 0UL; i < 3UL; ++i)
{
CHECK_EQUAL(compare_haystack.find(needles[i], position), haystack.find(needles[i], position));
CHECK_EQUAL(compare_haystack.rfind(needles[i], position), haystack.rfind(needles[i], position));
}
CHECK_EQUAL(compare_haystack.find(STR('e'), position), haystack.find(STR('e'), position));
CHECK_EQUAL(compare_haystack.rfind(STR('e'), position), haystack.rfind(STR('e'), position));
}
CHECK_EQUAL(TextL::npos, haystack.find(STR('e'), TextL::npos));
CHECK_EQUAL(TextL::npos, haystack.find(STR("needle"), TextL::npos));
}
//*************************************************************************
TEST_FIXTURE(SetupFixture, test_contains_string)
{
@ -3991,6 +4018,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4362,6 +4362,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4063,6 +4063,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4464,6 +4464,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4063,6 +4063,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4464,6 +4464,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4008,6 +4008,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4383,6 +4383,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -925,6 +925,27 @@ namespace
CHECK(View::npos == view.rfind(s4, 0, 11));
}
//*************************************************************************
TEST(test_find_rfind_all_positions)
{
const std::string compare_text("abcdabcdab");
View view(compare_text.c_str());
const char* needles[] = {"cd", "d", "x", ""};
// Every position up to past the end, where the search must not read outside the view.
for (size_t position = 0U; position <= compare_text.size() + 2U; ++position)
{
for (size_t i = 0U; i < 4U; ++i)
{
CHECK_EQUAL(compare_text.find(needles[i], position), view.find(needles[i], position));
CHECK_EQUAL(compare_text.rfind(needles[i], position), view.rfind(needles[i], position));
}
}
CHECK_EQUAL(View::npos, view.find("cd", View::npos));
}
//*************************************************************************
TEST(test_find_first_of)
{

View File

@ -4064,6 +4064,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -4467,6 +4467,11 @@ namespace
position2 = haystack.rfind(needle_view, haystack.size() - 10);
CHECK_EQUAL(position1, position2);
// The default position searches the whole string.
position1 = compare_haystack.rfind(compare_needle);
position2 = haystack.rfind(needle_view);
CHECK_EQUAL(position1, position2);
View pin_view(STR("pin"));
position2 = haystack.rfind(pin_view);
CHECK_EQUAL(TextL::npos, position2);

View File

@ -3,6 +3,16 @@ if(CONFIG_ETL)
zephyr_link_libraries(etl::etl)
zephyr_compile_definitions_ifdef(CONFIG_ETL_DEBUG ETL_DEBUG)
zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_PUSH_POP ETL_CHECK_PUSH_POP)
zephyr_compile_definitions_ifdef(CONFIG_ETL_LOG_ERRORS ETL_LOG_ERRORS)
zephyr_compile_definitions_ifdef(CONFIG_ETL_USE_ASSERT_FUNCTION ETL_USE_ASSERT_FUNCTION)
zephyr_compile_definitions_ifdef(CONFIG_ETL_THROW_EXCEPTIONS ETL_THROW_EXCEPTIONS)
zephyr_compile_definitions_ifdef(CONFIG_ETL_NO_CHECKS ETL_NO_CHECKS)
zephyr_compile_definitions_ifdef(CONFIG_ETL_VERBOSE_ERRORS ETL_VERBOSE_ERRORS)
zephyr_compile_definitions_ifdef(CONFIG_ETL_MINIMAL_ERRORS ETL_MINIMAL_ERRORS)
zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_PUSH_POP ETL_CHECK_PUSH_POP)
zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_INDEX_OPERATOR ETL_CHECK_INDEX_OPERATOR)
zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_EXTRA ETL_CHECK_EXTRA)
endif()

View File

@ -8,12 +8,78 @@ if ETL
config ETL_DEBUG
bool "Build ETL with debugging checks"
default y if DEBUG
default y if ASSERT
choice ETL_ERROR_MODE
prompt "ETL error handling mode"
help
What ETL does when one of its checks fails.
config ETL_ASSERT_ERRORS
bool "Assert in ETL_DEBUG builds, otherwise ignore"
help
With ETL_DEBUG, a failed check calls assert(). Without it, nothing
is reported, but checks that guard an operation still skip it.
With ETL_LOG_ERRORS, a failed check calls the error handler instead,
with or without ETL_DEBUG.
config ETL_USE_ASSERT_FUNCTION
bool "Call a user assert function"
help
A failed check calls the function registered with
etl::set_assert_function(), and checks that guard an operation
still skip it. Until one is registered, it calls assert(false).
config ETL_THROW_EXCEPTIONS
bool "Throw an exception"
depends on CPP_EXCEPTIONS
help
A failed check throws an etl::exception.
config ETL_NO_CHECKS
bool "No checks"
help
Check conditions are not evaluated.
endchoice # ETL_ERROR_MODE
config ETL_LOG_ERRORS
bool "Call the ETL error handler"
depends on !ETL_USE_ASSERT_FUNCTION && !ETL_NO_CHECKS
help
A failed check calls the callback registered with
etl::error_handler::set_callback(). With ETL_THROW_EXCEPTIONS it
then throws; otherwise, checks that guard an operation still skip
it. With no callback registered, errors are discarded.
choice ETL_ERROR_DETAIL
prompt "ETL error detail level"
help
What information the etl::exception for a failed check carries,
whether it is thrown or passed to the error handler or assert
function. Unused when a failed check calls plain assert().
config ETL_TERSE_ERRORS
bool "Short error code only"
config ETL_VERBOSE_ERRORS
bool "Message, file and line"
config ETL_MINIMAL_ERRORS
bool "Nothing"
endchoice # ETL_ERROR_DETAIL
config ETL_CHECK_PUSH_POP
bool "Enable push/pop checks in ETL"
depends on !ETL_NO_CHECKS
config ETL_LOG_ERRORS
bool "Call ETL error handler instead of assert"
config ETL_CHECK_INDEX_OPERATOR
bool "Enable bounds checks on ETL operator[]"
depends on !ETL_NO_CHECKS
config ETL_CHECK_EXTRA
bool "Enable extra bounds and empty checks in ETL containers"
depends on !ETL_NO_CHECKS
endif # ETL